Skip to content

fix: stabilize subgraph promoted widget identity and rendering#9896

Open
DrJKL wants to merge 8 commits intomainfrom
drjkl/fix/subgraph-widgets
Open

fix: stabilize subgraph promoted widget identity and rendering#9896
DrJKL wants to merge 8 commits intomainfrom
drjkl/fix/subgraph-widgets

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Mar 13, 2026

Summary

Fix subgraph promoted widget identity/rendering so on-node widgets stay correct through configure/hydration churn, duplicate names, and linked+independent coexistence.

Changes

  • Subgraph promotion reconciliation: stabilize linked-entry identity by subgraph slot id, preserve deterministic linked representative selection, and prune stale alias/fallback entries without dropping legitimate independent promotions.
  • Promoted view resolution: bind slot mapping by promoted view object identity (getSlotFromWidget / getWidgetFromSlot) to avoid same-name collisions.
  • On-node widget rendering: harden NodeWidgets identity and dedup to avoid visual aliasing, prefer visible duplicates over hidden stale entries, include type/source execution identity, and avoid collapsing transient unresolved entries.
  • Mapping correctness: update useGraphNodeManager promoted source mapping to resolve by input target only when the promoted view is actually bound to that input.
  • Subgraph input uniqueness: ensure empty-slot promotion creates unique input names (seed, seed_1, etc.) for same-name multi-source promotions.
  • Safety fix: guard against undefined canvas in slot-link interaction.
  • Tests/fixtures: add focused regressions for fixture path subgraph_complex_promotion_1, linked+independent same-name cases, duplicate-name identity mapping, dedup behavior, and input-name uniqueness.

Review Focus

Validate behavior around transient configure/hydration states (-1 id to concrete id), duplicate-name promotions, linked representative recovery, and that dedup never hides legitimate widgets while still removing true duplicates.

┆Issue is synchronized with this Notion page by Unito

DrJKL and others added 3 commits March 13, 2026 12:27
Add null checks for app.canvas in onClick and onDoubleClick handlers to prevent TypeError when canvas is not yet initialized.

Amp-Thread-ID: https://ampcode.com/threads/T-019ce8bc-bf85-778b-8f19-d1e2e715d797
Co-authored-by: Amp <amp@ampcode.com>
@github-actions
Copy link

github-actions bot commented Mar 13, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/14/2026, 08:27:42 AM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 13, 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
📝 Walkthrough

Walkthrough

Adds stable per-widget render keys and identity-based deduplication; shifts promoted-widget mapping from input-name to inputKey with per-node caches and rebinding; prefers linked input widget states for promoted reads/writes; introduces matchPromotedInput, widget render-key util, type guard, fixture helpers, and extensive tests.

Changes

Cohort / File(s) Summary
Widget rendering & dedupe
src/components/rightSidePanel/parameters/SectionWidgets.vue, src/renderer/extensions/vueNodes/components/NodeWidgets.vue, src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
Switch template keys to stable render keys; add identity-based deduplication and visibility-aware collapse; add renderKey to processed widgets and update tests for dedupe/visibility.
Stable widget render-key util & tests
src/core/graph/subgraph/widgetRenderKey.ts, src/core/graph/subgraph/widgetRenderKey.test.ts, src/components/rightSidePanel/parameters/SectionWidgets.vue
Add WeakMap-backed getStableWidgetRenderKey to generate cached deterministic keys (promoted vs widget prefixes); unit tests validate stability and uniqueness.
Promoted input matching helper & tests
src/core/graph/subgraph/matchPromotedInput.ts, src/core/graph/subgraph/matchPromotedInput.test.ts
New matchPromotedInput utility: prefers exact _widget matches, falls back to same-name when appropriate; includes unit tests for exact/fallback/multi-name cases.
Promoted widget mapping & linked state
src/composables/graph/useGraphNodeManager.ts, src/composables/graph/useGraphNodeManager.test.ts, src/core/graph/subgraph/promotedWidgetView.ts, src/core/graph/subgraph/promotedWidgetView.test.ts
Use matchPromotedInput to resolve promoted inputs and compute promotedSource conditionally; promote linked-input-first semantics for reads/writes; add helpers to find linked input widgets/states and expand promotion tests.
Promotion core: Subgraph node, IO, naming & caching
src/lib/litegraph/src/subgraph/SubgraphNode.ts, src/lib/litegraph/src/subgraph/SubgraphInputNode.ts, src/lib/litegraph/src/subgraph/SubgraphIO.test.ts, src/lib/litegraph/src/subgraph/SubgraphNode.test.ts
Migrate promotion keys from inputName→inputKey, add LinkedPromotionEntry/PromotionEntry types, per-node promoted-views cache with invalidation, rebinding of subgraph input slots, unique input name generation on connect, and many internal refactors to resolve/prune alias/fallback entries.
Fixtures & test helpers
src/lib/litegraph/src/subgraph/__fixtures__/subgraphComplexPromotion1.ts, src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts, src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts
Add large complex subgraph fixture and setup/cleanup helpers (fixture node type, graph/subgraph/host creation) plus tests ensuring fixture lifecycle cleanup.
Type guards, docs & minor guards
src/core/graph/subgraph/widgetNodeTypeGuard.ts, src/lib/litegraph/src/strings.ts, src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts, src/stores/promotionStore.ts
Add hasWidgetNode type guard; document nextUniqueName JSDoc; add early-return guards for missing canvas in click/double-click handlers; use stable EMPTY_PROMOTIONS ref in promotion store.
Extensive tests & test expansions
src/core/graph/subgraph/promotedWidgetView.test.ts, src/composables/graph/useGraphNodeManager.test.ts, src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts, src/lib/litegraph/src/subgraph/__fixtures__/*, src/stores/promotionStore.test.ts
Large additions and expansions of tests covering promotion reconciliation, aliasing, serialization round-trips, value sharing, DOM/component integration, dedupe, uniqueness, and many edge cases.

Sequence Diagram

sequenceDiagram
    participant SubgraphNode as SubgraphNode
    participant Cache as PromotedViewsCache
    participant PW as PromotedWidgetView
    participant WidgetStore as WidgetValueStore
    participant NodeWidgets as NodeWidgetsComponent
    participant Renderer as Renderer

    SubgraphNode->>Cache: read cached promoted views (by signature)
    alt cache hit
        Cache-->>SubgraphNode: return cached promoted views
    else cache miss
        SubgraphNode->>SubgraphNode: resolve linked promotions by subgraphInput (inputKey)
        SubgraphNode->>SubgraphNode: build PromotionEntry[] and keys
        SubgraphNode->>PW: create/update promoted views for each entry
        PW->>WidgetStore: read/write linked input widget states
        PW-->>SubgraphNode: return promoted widget descriptors
        SubgraphNode->>Cache: write promoted views with new signature
    end
    SubgraphNode-->>NodeWidgets: provide combined widget list (native + promoted)
    NodeWidgets->>NodeWidgets: compute widget identity & renderKey
    NodeWidgets->>NodeWidgets: deduplicate by identity (prefer visible)
    NodeWidgets->>Renderer: render deduplicated widgets with stable keys
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • christian-byrne

Poem

🐇 I hopped through nodes and keys so bright,

Matched hidden widgets into light.
Linked states whisper, caches hum,
Keys held steady, duplicates come—
A tidy render, all just right.

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
End-To-End Regression Coverage For Fixes ⚠️ Warning PR uses bug-fix language in title but lacks Playwright end-to-end regression tests in browser_tests/ directory, with no concrete explanation provided for their omission. Add Playwright e2e regression test under browser_tests/ or provide concrete explanation in PR description for why e2e testing is not feasible.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: stabilize subgraph promoted widget identity and rendering' directly matches the main change focus: stabilizing promoted widget identity and rendering behavior in subgraph contexts.
Description check ✅ Passed The PR description includes a Summary, Changes section with detailed bullet points covering the work, and Review Focus section. It substantially exceeds the template requirements with concrete implementation details and test coverage information.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drjkl/fix/subgraph-widgets
📝 Coding Plan
  • Generate coding plan for human review comments

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

@github-actions
Copy link

github-actions bot commented Mar 13, 2026

🎭 Playwright: ✅ 557 passed, 0 failed · 6 flaky

📊 Browser Reports
  • chromium: View Report (✅ 544 / ❌ 0 / ⚠️ 6 / ⏭️ 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)

@github-actions
Copy link

github-actions bot commented Mar 13, 2026

📦 Bundle: 4.98 MB gzip 🔴 +2.72 kB

Details

Summary

  • Raw size: 23 MB baseline 23 MB — 🔴 +12.8 kB
  • Gzip: 4.98 MB baseline 4.98 MB — 🔴 +2.72 kB
  • Brotli: 3.84 MB baseline 3.84 MB — 🔴 +2.18 kB
  • Bundles: 235 current • 235 baseline • 112 added / 112 removed

Category Glance
Data & Services 🔴 +12.4 kB (3.17 MB) · Graph Workspace 🔴 +426 B (1.08 MB) · Vendor & Third-Party ⚪ 0 B (9.78 MB) · Other ⚪ 0 B (8.18 MB) · Panels & Settings ⚪ 0 B (456 kB) · Editors & Dialogs ⚪ 0 B (81.8 kB) · + 5 more

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

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-BapdAIqx.js (new) 21.7 kB 🔴 +21.7 kB 🔴 +7.71 kB 🔴 +6.63 kB
assets/index-Mh5jRLCR.js (removed) 21.7 kB 🟢 -21.7 kB 🟢 -7.71 kB 🟢 -6.64 kB

Status: 1 added / 1 removed

Graph Workspace — 1.08 MB (baseline 1.08 MB) • 🔴 +426 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CXlVEzBt.js (new) 1.08 MB 🔴 +1.08 MB 🔴 +230 kB 🔴 +174 kB
assets/GraphView-BPaiiSfG.js (removed) 1.08 MB 🟢 -1.08 MB 🟢 -230 kB 🟢 -174 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-DJkOj8dS.js (new) 15.6 kB 🔴 +15.6 kB 🔴 +3.38 kB 🔴 +2.88 kB
assets/CloudSurveyView-es5f7SKi.js (removed) 15.6 kB 🟢 -15.6 kB 🟢 -3.38 kB 🟢 -2.89 kB
assets/CloudLoginView-Cc6kcwTW.js (removed) 11.8 kB 🟢 -11.8 kB 🟢 -3.27 kB 🟢 -2.88 kB
assets/CloudLoginView-CePmePmm.js (new) 11.8 kB 🔴 +11.8 kB 🔴 +3.27 kB 🔴 +2.88 kB
assets/CloudSignupView-B7UVuuAV.js (new) 9.52 kB 🔴 +9.52 kB 🔴 +2.76 kB 🔴 +2.42 kB
assets/CloudSignupView-CtkWAsAw.js (removed) 9.52 kB 🟢 -9.52 kB 🟢 -2.75 kB 🟢 -2.41 kB
assets/UserCheckView-BLMq5p-d.js (removed) 9.01 kB 🟢 -9.01 kB 🟢 -2.31 kB 🟢 -2.01 kB
assets/UserCheckView-BWg_8_WH.js (new) 9.01 kB 🔴 +9.01 kB 🔴 +2.31 kB 🔴 +2.01 kB
assets/CloudLayoutView-CKnBQtpr.js (new) 7.3 kB 🔴 +7.3 kB 🔴 +2.27 kB 🔴 +1.96 kB
assets/CloudLayoutView-zSWJnaS0.js (removed) 7.3 kB 🟢 -7.3 kB 🟢 -2.27 kB 🟢 -1.97 kB
assets/CloudForgotPasswordView-BWT_2aIp.js (new) 5.73 kB 🔴 +5.73 kB 🔴 +2 kB 🔴 +1.75 kB
assets/CloudForgotPasswordView-D3JCVShy.js (removed) 5.73 kB 🟢 -5.73 kB 🟢 -2 kB 🟢 -1.75 kB
assets/CloudAuthTimeoutView-B8iP37kV.js (new) 5.09 kB 🔴 +5.09 kB 🔴 +1.83 kB 🔴 +1.61 kB
assets/CloudAuthTimeoutView-BgSqAlZ9.js (removed) 5.09 kB 🟢 -5.09 kB 🟢 -1.84 kB 🟢 -1.59 kB
assets/CloudSubscriptionRedirectView-B7DYlBpS.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.84 kB 🟢 -1.62 kB
assets/CloudSubscriptionRedirectView-BNUXTLUt.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.84 kB 🔴 +1.63 kB
assets/UserSelectView-_VwBslKC.js (new) 4.67 kB 🔴 +4.67 kB 🔴 +1.73 kB 🔴 +1.53 kB
assets/UserSelectView-ClGs_g7v.js (removed) 4.67 kB 🟢 -4.67 kB 🟢 -1.73 kB 🟢 -1.53 kB
assets/CloudSorryContactSupportView-DVucrv0K.js 1.21 kB 1.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-BE0wPzm2.js 385 B 385 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/KeybindingPanel-D6KtNU3q.js (removed) 28.8 kB 🟢 -28.8 kB 🟢 -6.14 kB 🟢 -5.48 kB
assets/KeybindingPanel-Dhzf6t_s.js (new) 28.8 kB 🔴 +28.8 kB 🔴 +6.14 kB 🔴 +5.46 kB
assets/SecretsPanel-Bxe3i_WG.js (new) 22.3 kB 🔴 +22.3 kB 🔴 +5.41 kB 🔴 +4.75 kB
assets/SecretsPanel-DNYro82m.js (removed) 22.3 kB 🟢 -22.3 kB 🟢 -5.41 kB 🟢 -4.76 kB
assets/LegacyCreditsPanel-BXxahnJb.js (removed) 21.2 kB 🟢 -21.2 kB 🟢 -5.67 kB 🟢 -4.99 kB
assets/LegacyCreditsPanel-CZRryp-j.js (new) 21.2 kB 🔴 +21.2 kB 🔴 +5.67 kB 🔴 +4.99 kB
assets/SubscriptionPanel-BKSnUDYH.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.83 kB 🟢 -4.25 kB
assets/SubscriptionPanel-DtdV-Ofa.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.83 kB 🔴 +4.26 kB
assets/AboutPanel-BkN4MJAW.js (new) 11.9 kB 🔴 +11.9 kB 🔴 +3.3 kB 🔴 +2.95 kB
assets/AboutPanel-ytqOR77r.js (removed) 11.9 kB 🟢 -11.9 kB 🟢 -3.3 kB 🟢 -2.97 kB
assets/ExtensionPanel-CeHXUF6w.js (new) 9.54 kB 🔴 +9.54 kB 🔴 +2.71 kB 🔴 +2.41 kB
assets/ExtensionPanel-UNIT9liY.js (removed) 9.54 kB 🟢 -9.54 kB 🟢 -2.71 kB 🟢 -2.41 kB
assets/ServerConfigPanel-Ddg6z201.js (new) 6.62 kB 🔴 +6.62 kB 🔴 +2.19 kB 🔴 +1.96 kB
assets/ServerConfigPanel-uX4eT8qb.js (removed) 6.62 kB 🟢 -6.62 kB 🟢 -2.19 kB 🟢 -1.99 kB
assets/UserPanel-D0WopMrK.js (new) 6.33 kB 🔴 +6.33 kB 🔴 +2.06 kB 🔴 +1.79 kB
assets/UserPanel-Du2KjmIE.js (removed) 6.33 kB 🟢 -6.33 kB 🟢 -2.05 kB 🟢 -1.8 kB
assets/cloudRemoteConfig-BAxRhkHT.js (removed) 1.62 kB 🟢 -1.62 kB 🟢 -799 B 🟢 -680 B
assets/cloudRemoteConfig-D9SK-kwE.js (new) 1.62 kB 🔴 +1.62 kB 🔴 +800 B 🔴 +683 B
assets/refreshRemoteConfig-CAGI47YJ.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -649 B 🟢 -561 B
assets/refreshRemoteConfig-DonoOXkK.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +649 B 🔴 +556 B
assets/config-BazmIiy7.js 1.79 kB 1.79 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B2ZrLCMe.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B7w1QSUI.js 24.3 kB 24.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BCix_ovw.js 27.6 kB 27.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BuZxsHv9.js 28.6 kB 28.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-C1Y6QG4O.js 30.4 kB 30.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CKm2Tb8F.js 28.6 kB 28.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CMAnYFoB.js 32.2 kB 32.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CwecEjOW.js 38.4 kB 38.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DAqquczG.js 23.7 kB 23.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DPg0tq2T.js 34 kB 34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DTGtSEof.js 29.8 kB 29.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-Bqiq3Bgx.js (new) 3.57 kB 🔴 +3.57 kB 🔴 +1.26 kB 🔴 +1.07 kB
assets/auth-CoRfq0M_.js (removed) 3.57 kB 🟢 -3.57 kB 🟢 -1.26 kB 🟢 -1.07 kB
assets/SignUpForm-B1-Ek8Xv.js (removed) 3.18 kB 🟢 -3.18 kB 🟢 -1.29 kB 🟢 -1.15 kB
assets/SignUpForm-y-ulDnEQ.js (new) 3.18 kB 🔴 +3.18 kB 🔴 +1.29 kB 🔴 +1.15 kB
assets/UpdatePasswordContent-BJOWMS_F.js (removed) 2.44 kB 🟢 -2.44 kB 🟢 -1.09 kB 🟢 -969 B
assets/UpdatePasswordContent-zGXUYrKc.js (new) 2.44 kB 🔴 +2.44 kB 🔴 +1.1 kB 🔴 +969 B
assets/firebaseAuthStore-6l3lubSr.js (removed) 788 B 🟢 -788 B 🟢 -387 B 🟢 -342 B
assets/firebaseAuthStore-CTCjCCGC.js (new) 788 B 🔴 +788 B 🔴 +390 B 🔴 +344 B
assets/auth-_Wkcp8d3.js (removed) 313 B 🟢 -313 B 🟢 -200 B 🟢 -183 B
assets/auth-CwF9JwjA.js (new) 313 B 🔴 +313 B 🔴 +199 B 🔴 +189 B
assets/PasswordFields-DcCUqFkG.js 4.68 kB 4.68 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-CVvFhrNa.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-BrgOj7iN.js (removed) 81 kB 🟢 -81 kB 🟢 -16.9 kB 🟢 -14.5 kB
assets/useShareDialog-C0XjTTNS.js (new) 81 kB 🔴 +81 kB 🔴 +16.9 kB 🔴 +14.5 kB
assets/useSubscriptionDialog-DM_de8e-.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -327 B
assets/useSubscriptionDialog-LUwEhUjS.js (new) 736 B 🔴 +736 B 🔴 +382 B 🔴 +330 B

Status: 2 added / 2 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-BCqI94V9.js (new) 14.3 kB 🔴 +14.3 kB 🔴 +4 kB 🔴 +3.57 kB
assets/ComfyQueueButton-BTbwZG38.js (removed) 14.3 kB 🟢 -14.3 kB 🟢 -4.01 kB 🟢 -3.58 kB
assets/useTerminalTabs-BO-5_DER.js (new) 10.4 kB 🔴 +10.4 kB 🔴 +3.5 kB 🔴 +3.08 kB
assets/useTerminalTabs-DMH0f5xQ.js (removed) 10.4 kB 🟢 -10.4 kB 🟢 -3.5 kB 🟢 -3.08 kB
assets/SubscribeButton-Bqej_u-0.js (removed) 2.42 kB 🟢 -2.42 kB 🟢 -1.04 kB 🟢 -915 B
assets/SubscribeButton-Xw6QlNol.js (new) 2.42 kB 🔴 +2.42 kB 🔴 +1.05 kB 🔴 +918 B
assets/cloudFeedbackTopbarButton-BM8ZFewI.js (removed) 1.43 kB 🟢 -1.43 kB 🟢 -744 B 🟢 -661 B
assets/cloudFeedbackTopbarButton-uNxCJCR0.js (new) 1.43 kB 🔴 +1.43 kB 🔴 +745 B 🔴 +668 B
assets/ComfyQueueButton-BLaa3IzX.js (removed) 793 B 🟢 -793 B 🟢 -394 B 🟢 -352 B
assets/ComfyQueueButton-Ds0p1l_p.js (new) 793 B 🔴 +793 B 🔴 +398 B 🔴 +346 B
assets/Button-CzCLfUsG.js 3.42 kB 3.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-B8l4Ioh2.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-BpEgMicB.js 3.94 kB 3.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-Dx7LngHp.js 6.27 kB 6.27 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/toggle-group-By5E1G-i.js 4.03 kB 4.03 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-Bnm3BoM3.js 7.53 kB 7.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-XBmTe5nW.js 1.24 kB 1.24 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-C5HuExH9.js 2.04 kB 2.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 3.17 MB (baseline 3.16 MB) • 🔴 +12.4 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-CxS4xF6b.js (new) 2.15 MB 🔴 +2.15 MB 🔴 +495 kB 🔴 +370 kB
assets/dialogService-CkYJsIyt.js (removed) 2.15 MB 🟢 -2.15 MB 🟢 -494 kB 🟢 -370 kB
assets/api-DuWPn9Jm.js (new) 862 kB 🔴 +862 kB 🔴 +206 kB 🔴 +162 kB
assets/api-Dypg3pwT.js (removed) 852 kB 🟢 -852 kB 🟢 -204 kB 🟢 -161 kB
assets/load3dService-BTzFopA3.js (removed) 93.3 kB 🟢 -93.3 kB 🟢 -19.8 kB 🟢 -17 kB
assets/load3dService-Di5F7Ruo.js (new) 93.3 kB 🔴 +93.3 kB 🔴 +19.8 kB 🔴 +17 kB
assets/extensionStore-CsRjYBWT.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -6.7 kB 🟢 -5.86 kB
assets/extensionStore-D7-5AeNt.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +6.7 kB 🔴 +5.87 kB
assets/workflowShareService-BnPx4QTu.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +4.32 kB 🔴 +3.8 kB
assets/workflowShareService-zt4ps72R.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -4.32 kB 🟢 -3.8 kB
assets/releaseStore-11twFo-i.js (removed) 8.07 kB 🟢 -8.07 kB 🟢 -2.25 kB 🟢 -1.98 kB
assets/releaseStore-CnOFkK_U.js (new) 8.07 kB 🔴 +8.07 kB 🔴 +2.25 kB 🔴 +1.98 kB
assets/keybindingService-Cd3nW6wp.js (removed) 6.99 kB 🟢 -6.99 kB 🟢 -1.73 kB 🟢 -1.49 kB
assets/keybindingService-r1kitDeE.js (new) 6.99 kB 🔴 +6.99 kB 🔴 +1.74 kB 🔴 +1.5 kB
assets/userStore-BzS3oJtm.js (new) 2.24 kB 🔴 +2.24 kB 🔴 +868 B 🔴 +767 B
assets/userStore-CKWIjlUS.js (removed) 2.24 kB 🟢 -2.24 kB 🟢 -869 B 🟢 -770 B
assets/bootstrapStore-BBMPMfbg.js (new) 2.11 kB 🔴 +2.11 kB 🔴 +889 B 🔴 +809 B
assets/bootstrapStore-BdyCbK6P.js (removed) 2.11 kB 🟢 -2.11 kB 🟢 -888 B 🟢 -804 B
assets/audioService-D0x_B9V1.js (new) 1.75 kB 🔴 +1.75 kB 🔴 +865 B 🔴 +745 B
assets/audioService-Y9YVSJpS.js (removed) 1.75 kB 🟢 -1.75 kB 🟢 -864 B 🟢 -746 B
assets/releaseStore-BdvE1oPy.js (new) 760 B 🔴 +760 B 🔴 +389 B 🔴 +339 B
assets/releaseStore-BMkTEUV2.js (removed) 760 B 🟢 -760 B 🟢 -384 B 🟢 -341 B
assets/workflowDraftStore-WOQfgtMu.js (new) 736 B 🔴 +736 B 🔴 +383 B 🔴 +337 B
assets/workflowDraftStore-zIECHN9b.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -335 B
assets/dialogService-CMn4TLBn.js (new) 725 B 🔴 +725 B 🔴 +374 B 🔴 +330 B
assets/dialogService-CnRGj169.js (removed) 725 B 🟢 -725 B 🟢 -370 B 🟢 -325 B
assets/settingStore-CHeojbS_.js (removed) 723 B 🟢 -723 B 🟢 -373 B 🟢 -326 B
assets/settingStore-hKrBo_W4.js (new) 723 B 🔴 +723 B 🔴 +377 B 🔴 +329 B
assets/assetsStore-C-4uBCUV.js (new) 722 B 🔴 +722 B 🔴 +373 B 🔴 +329 B
assets/assetsStore-CtV-KlAH.js (removed) 722 B 🟢 -722 B 🟢 -369 B 🟢 -327 B
assets/serverConfigStore-BMw7voHC.js 2.35 kB 2.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 15 added / 15 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3dViewer-DsexNdcD.js (removed) 15.1 kB 🟢 -15.1 kB 🟢 -3.42 kB 🟢 -3.03 kB
assets/useLoad3dViewer-H9L1elPc.js (new) 15.1 kB 🔴 +15.1 kB 🔴 +3.42 kB 🔴 +3.02 kB
assets/useLoad3d-B4Hu767u.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.65 kB 🔴 +3.23 kB
assets/useLoad3d-BJ_Wu9up.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.65 kB 🟢 -3.23 kB
assets/useFeatureFlags-CJ3kbHC-.js (removed) 5.78 kB 🟢 -5.78 kB 🟢 -1.75 kB 🟢 -1.48 kB
assets/useFeatureFlags-ZqxsbMkM.js (new) 5.78 kB 🔴 +5.78 kB 🔴 +1.75 kB 🔴 +1.49 kB
assets/useWorkspaceUI-C9Q3dQHU.js (new) 3.34 kB 🔴 +3.34 kB 🔴 +980 B 🔴 +812 B
assets/useWorkspaceUI-Du4kRyzQ.js (removed) 3.34 kB 🟢 -3.34 kB 🟢 -979 B 🟢 -813 B
assets/subscriptionCheckoutUtil-B1E5xNPw.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.32 kB 🔴 +1.15 kB
assets/subscriptionCheckoutUtil-CJc-v-2T.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.31 kB 🟢 -1.15 kB
assets/useUpstreamValue-C2KsMfLe.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +808 B 🔴 +721 B
assets/useUpstreamValue-SABdwNUZ.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -803 B 🟢 -712 B
assets/useErrorHandling-_nOMz14A.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +652 B 🔴 +556 B
assets/useErrorHandling-rG9-oBYK.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -649 B 🟢 -554 B
assets/audioUtils-BLNXphkW.js (new) 958 B 🔴 +958 B 🔴 +564 B 🔴 +492 B
assets/audioUtils-D_LvKQ8U.js (removed) 958 B 🟢 -958 B 🟢 -566 B 🟢 -460 B
assets/useLoad3d-4EuYT1yy.js (new) 859 B 🔴 +859 B 🔴 +427 B 🔴 +379 B
assets/useLoad3d-CmQ7WgGu.js (removed) 859 B 🟢 -859 B 🟢 -425 B 🟢 -381 B
assets/useLoad3dViewer-AtXjRcnP.js (removed) 838 B 🟢 -838 B 🟢 -409 B 🟢 -366 B
assets/useLoad3dViewer-CVcMPpNp.js (new) 838 B 🔴 +838 B 🔴 +413 B 🔴 +369 B
assets/useWorkspaceSwitch-BEcfSbSx.js (new) 747 B 🔴 +747 B 🔴 +386 B 🔴 +333 B
assets/useWorkspaceSwitch-Bv6ZWWPf.js (removed) 747 B 🟢 -747 B 🟢 -386 B 🟢 -330 B
assets/useCurrentUser-BnpaooFK.js (removed) 722 B 🟢 -722 B 🟢 -373 B 🟢 -327 B
assets/useCurrentUser-Dgcp8jik.js (new) 722 B 🔴 +722 B 🔴 +377 B 🔴 +330 B
assets/_plugin-vue_export-helper-DqNI4win.js 365 B 365 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/assetMetadataUtils-Cu1m4aen.js 4.78 kB 4.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-BoDX6eKf.js 8.89 kB 8.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-BOOGgtql.js 489 B 489 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-AGO63igr.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-BrLYgVOH.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-DZQsd5AS.js 3.04 kB 3.04 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 12 added / 12 removed

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

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-B-zaJ78_.js 93 kB 93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-QIvOlSgA.js 411 kB 411 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-x5F51RZV.js 1.01 MB 1.01 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-TjUfhse9.js 140 kB 140 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-BAquA4iy.js 110 kB 110 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-DD8n2cnE.js 1.76 MB 1.76 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-DGRjWEoA.js 1.75 MB 1.75 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-CCjqphhL.js 474 kB 474 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-Dn2jSJwd.js 267 kB 267 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-D0iKHrgP.js 1.83 MB 1.83 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-bePjZBYs.js 737 kB 737 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-Ba0aGEmU.js 328 kB 328 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-DrtiTSko.js 136 kB 136 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-DZ7n4cB7.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-B0izGxZ6.js 246 kB 246 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-W_VsqAhz.js 111 kB 111 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 8.18 MB (baseline 8.18 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-Cd-lbk8o.js (new) 75.7 kB 🔴 +75.7 kB 🔴 +19.5 kB 🔴 +16.7 kB
assets/core-Cvx9BgOd.js (removed) 75.7 kB 🟢 -75.7 kB 🟢 -19.5 kB 🟢 -16.7 kB
assets/groupNode-CMgusTTE.js (removed) 73.9 kB 🟢 -73.9 kB 🟢 -18.5 kB 🟢 -16.3 kB
assets/groupNode-THy_f-us.js (new) 73.9 kB 🔴 +73.9 kB 🔴 +18.5 kB 🔴 +16.3 kB
assets/WidgetSelect-Br1yOKbR.js (new) 63.2 kB 🔴 +63.2 kB 🔴 +13.8 kB 🔴 +11.9 kB
assets/WidgetSelect-DoYR1kKq.js (removed) 63.2 kB 🟢 -63.2 kB 🟢 -13.8 kB 🟢 -11.9 kB
assets/SubscriptionRequiredDialogContentWorkspace-B-cvka_A.js (new) 47.1 kB 🔴 +47.1 kB 🔴 +8.76 kB 🔴 +7.56 kB
assets/SubscriptionRequiredDialogContentWorkspace-DOUgsBWo.js (removed) 47.1 kB 🟢 -47.1 kB 🟢 -8.76 kB 🟢 -7.58 kB
assets/WidgetPainter-B3jS1COr.js (removed) 33.1 kB 🟢 -33.1 kB 🟢 -8.01 kB 🟢 -7.1 kB
assets/WidgetPainter-C20cnlwm.js (new) 33.1 kB 🔴 +33.1 kB 🔴 +8.01 kB 🔴 +7.11 kB
assets/Load3DControls-4mJPo0PU.js (removed) 32.1 kB 🟢 -32.1 kB 🟢 -5.47 kB 🟢 -4.75 kB
assets/Load3DControls-CH4GkbLo.js (new) 32.1 kB 🔴 +32.1 kB 🔴 +5.47 kB 🔴 +4.75 kB
assets/WorkspacePanelContent-coXe2wxP.js (removed) 29.7 kB 🟢 -29.7 kB 🟢 -6.23 kB 🟢 -5.46 kB
assets/WorkspacePanelContent-DdcfZd2b.js (new) 29.7 kB 🔴 +29.7 kB 🔴 +6.23 kB 🔴 +5.46 kB
assets/SubscriptionRequiredDialogContent-CQgWiqLy.js (removed) 26.1 kB 🟢 -26.1 kB 🟢 -6.62 kB 🟢 -5.85 kB
assets/SubscriptionRequiredDialogContent-CtDGGXzS.js (new) 26.1 kB 🔴 +26.1 kB 🔴 +6.62 kB 🔴 +5.83 kB
assets/Load3dViewerContent-V6DdCcn2.js (new) 24.3 kB 🔴 +24.3 kB 🔴 +5.32 kB 🔴 +4.63 kB
assets/Load3dViewerContent-wDT4woBm.js (removed) 24.3 kB 🟢 -24.3 kB 🟢 -5.31 kB 🟢 -4.63 kB
assets/WidgetImageCrop-Du1FeUsP.js (removed) 23.1 kB 🟢 -23.1 kB 🟢 -5.75 kB 🟢 -5.06 kB
assets/WidgetImageCrop-FBJEif_j.js (new) 23.1 kB 🔴 +23.1 kB 🔴 +5.75 kB 🔴 +5.07 kB
assets/SubscriptionPanelContentWorkspace-BoVOh03M.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.18 kB 🔴 +4.56 kB
assets/SubscriptionPanelContentWorkspace-gTOfIqm6.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.18 kB 🟢 -4.55 kB
assets/CurrentUserPopoverWorkspace-Bk3skjWe.js (removed) 20.8 kB 🟢 -20.8 kB 🟢 -5 kB 🟢 -4.47 kB
assets/CurrentUserPopoverWorkspace-Dmdm9hZi.js (new) 20.8 kB 🔴 +20.8 kB 🔴 +5 kB 🔴 +4.47 kB
assets/SignInContent-DtWR08B2.js (new) 20 kB 🔴 +20 kB 🔴 +5.16 kB 🔴 +4.5 kB
assets/SignInContent-DwC6bEL6.js (removed) 20 kB 🟢 -20 kB 🟢 -5.16 kB 🟢 -4.49 kB
assets/WidgetInputNumber-BJ72jHhV.js (new) 19.1 kB 🔴 +19.1 kB 🔴 +4.84 kB 🔴 +4.29 kB
assets/WidgetInputNumber-fcYoGzBl.js (removed) 19.1 kB 🟢 -19.1 kB 🟢 -4.84 kB 🟢 -4.29 kB
assets/WidgetRecordAudio-B5wSIzic.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -5.1 kB 🟢 -4.56 kB
assets/WidgetRecordAudio-emFZkWgB.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +5.1 kB 🔴 +4.57 kB
assets/Load3D-11XTXdII.js (new) 16.8 kB 🔴 +16.8 kB 🔴 +4.11 kB 🔴 +3.6 kB
assets/Load3D-Vxo29MLY.js (removed) 16.8 kB 🟢 -16.8 kB 🟢 -4.12 kB 🟢 -3.59 kB
assets/load3d-D3mTb52r.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.21 kB 🔴 +3.65 kB
assets/load3d-pgMZXPZn.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.21 kB 🟢 -3.65 kB
assets/WidgetCurve-DcA4b2LU.js (new) 11.7 kB 🔴 +11.7 kB 🔴 +3.9 kB 🔴 +3.5 kB
assets/WidgetCurve-eX0Wc4Zi.js (removed) 11.7 kB 🟢 -11.7 kB 🟢 -3.9 kB 🟢 -3.51 kB
assets/AudioPreviewPlayer-BooJJvWX.js (new) 11.2 kB 🔴 +11.2 kB 🔴 +3.31 kB 🔴 +2.96 kB
assets/AudioPreviewPlayer-C0zyIjUi.js (removed) 11.2 kB 🟢 -11.2 kB 🟢 -3.3 kB 🟢 -2.96 kB
assets/nodeTemplates-DwP9YKhN.js (new) 9.33 kB 🔴 +9.33 kB 🔴 +3.28 kB 🔴 +2.88 kB
assets/nodeTemplates-E7yRH4C1.js (removed) 9.33 kB 🟢 -9.33 kB 🟢 -3.28 kB 🟢 -2.88 kB
assets/InviteMemberDialogContent-D0EgACU5.js (new) 7.53 kB 🔴 +7.53 kB 🔴 +2.36 kB 🔴 +2.06 kB
assets/InviteMemberDialogContent-Dt_nH8DA.js (removed) 7.53 kB 🟢 -7.53 kB 🟢 -2.36 kB 🟢 -2.06 kB
assets/Load3DConfiguration-BbXKE6Uz.js (removed) 6.55 kB 🟢 -6.55 kB 🟢 -2.03 kB 🟢 -1.77 kB
assets/Load3DConfiguration-DpDj2fO2.js (new) 6.55 kB 🔴 +6.55 kB 🔴 +2.03 kB 🔴 +1.77 kB
assets/onboardingCloudRoutes-C7cXKpbB.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.91 kB 🟢 -1.67 kB
assets/onboardingCloudRoutes-DsPXg0vr.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +1.91 kB 🔴 +1.65 kB
assets/WidgetWithControl-HRg3-puq.js (new) 5.76 kB 🔴 +5.76 kB 🔴 +2.25 kB 🔴 +2.02 kB
assets/WidgetWithControl-nXlB0vNl.js (removed) 5.76 kB 🟢 -5.76 kB 🟢 -2.25 kB 🟢 -2.01 kB
assets/CreateWorkspaceDialogContent-B6jP92w2.js (new) 5.71 kB 🔴 +5.71 kB 🔴 +2.06 kB 🔴 +1.8 kB
assets/CreateWorkspaceDialogContent-DvFd_X2G.js (removed) 5.71 kB 🟢 -5.71 kB 🟢 -2.05 kB 🟢 -1.78 kB
assets/FreeTierDialogContent-C3NbN8HW.js (removed) 5.54 kB 🟢 -5.54 kB 🟢 -1.94 kB 🟢 -1.71 kB
assets/FreeTierDialogContent-vkdL4Cye.js (new) 5.54 kB 🔴 +5.54 kB 🔴 +1.94 kB 🔴 +1.71 kB
assets/EditWorkspaceDialogContent-B5dMYNgX.js (removed) 5.51 kB 🟢 -5.51 kB 🟢 -2.01 kB 🟢 -1.76 kB
assets/EditWorkspaceDialogContent-C-xK1oHX.js (new) 5.51 kB 🔴 +5.51 kB 🔴 +2.02 kB 🔴 +1.76 kB
assets/ValueControlPopover-CHHTWbdU.js (new) 5.1 kB 🔴 +5.1 kB 🔴 +1.82 kB 🔴 +1.63 kB
assets/ValueControlPopover-sYdhvEfz.js (removed) 5.1 kB 🟢 -5.1 kB 🟢 -1.82 kB 🟢 -1.63 kB
assets/Preview3d-DbrYNt9g.js (removed) 5.08 kB 🟢 -5.08 kB 🟢 -1.67 kB 🟢 -1.45 kB
assets/Preview3d-jn3UC_zh.js (new) 5.08 kB 🔴 +5.08 kB 🔴 +1.67 kB 🔴 +1.46 kB
assets/WidgetTextarea-BIFUBvev.js (removed) 5.03 kB 🟢 -5.03 kB 🟢 -1.97 kB 🟢 -1.76 kB
assets/WidgetTextarea-CsTIm0l3.js (new) 5.03 kB 🔴 +5.03 kB 🔴 +1.97 kB 🔴 +1.74 kB
assets/CancelSubscriptionDialogContent-C9Zmrzqw.js (removed) 4.98 kB 🟢 -4.98 kB 🟢 -1.85 kB 🟢 -1.61 kB
assets/CancelSubscriptionDialogContent-CaYG_bxa.js (new) 4.98 kB 🔴 +4.98 kB 🔴 +1.85 kB 🔴 +1.62 kB
assets/tierBenefits-BKKavuP2.js (new) 4.47 kB 🔴 +4.47 kB 🔴 +1.58 kB 🔴 +1.36 kB
assets/tierBenefits-q1OZVt9k.js (removed) 4.47 kB 🟢 -4.47 kB 🟢 -1.58 kB 🟢 -1.37 kB
assets/DeleteWorkspaceDialogContent-CClNUhyD.js (removed) 4.41 kB 🟢 -4.41 kB 🟢 -1.69 kB 🟢 -1.47 kB
assets/DeleteWorkspaceDialogContent-uKWKOkve.js (new) 4.41 kB 🔴 +4.41 kB 🔴 +1.7 kB 🔴 +1.47 kB
assets/LeaveWorkspaceDialogContent-6YxlPAFF.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.64 kB 🟢 -1.42 kB
assets/LeaveWorkspaceDialogContent-CEg7NHkd.js (new) 4.24 kB 🔴 +4.24 kB 🔴 +1.64 kB 🔴 +1.42 kB
assets/RemoveMemberDialogContent-Dqf4ePfk.js (removed) 4.22 kB 🟢 -4.22 kB 🟢 -1.59 kB 🟢 -1.39 kB
assets/RemoveMemberDialogContent-DRvKaTJ6.js (new) 4.22 kB 🔴 +4.22 kB 🔴 +1.6 kB 🔴 +1.39 kB
assets/RevokeInviteDialogContent-7Rsw53lT.js (removed) 4.13 kB 🟢 -4.13 kB 🟢 -1.6 kB 🟢 -1.4 kB
assets/RevokeInviteDialogContent-zShAZAhV.js (new) 4.13 kB 🔴 +4.13 kB 🔴 +1.61 kB 🔴 +1.4 kB
assets/InviteMemberUpsellDialogContent-DyZspIny.js (removed) 4.03 kB 🟢 -4.03 kB 🟢 -1.46 kB 🟢 -1.28 kB
assets/InviteMemberUpsellDialogContent-XOjKOmss.js (new) 4.03 kB 🔴 +4.03 kB 🔴 +1.47 kB 🔴 +1.28 kB
assets/cloudSessionCookie-14Fd6-PA.js (removed) 3.9 kB 🟢 -3.9 kB 🟢 -1.38 kB 🟢 -1.2 kB
assets/cloudSessionCookie-Bd92gaAE.js (new) 3.9 kB 🔴 +3.9 kB 🔴 +1.39 kB 🔴 +1.21 kB
assets/saveMesh-CxA0Zj_n.js (new) 3.42 kB 🔴 +3.42 kB 🔴 +1.47 kB 🔴 +1.31 kB
assets/saveMesh-oCMXmN9w.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -1.47 kB 🟢 -1.3 kB
assets/GlobalToast-BUYycNw6.js (new) 3.04 kB 🔴 +3.04 kB 🔴 +1.26 kB 🔴 +1.08 kB
assets/GlobalToast-DQ0lEKdn.js (removed) 3.04 kB 🟢 -3.04 kB 🟢 -1.26 kB 🟢 -1.08 kB
assets/SubscribeToRun-CDR7PvrJ.js (removed) 2.13 kB 🟢 -2.13 kB 🟢 -979 B 🟢 -872 B
assets/SubscribeToRun-CxTQQDks.js (new) 2.13 kB 🔴 +2.13 kB 🔴 +978 B 🔴 +857 B
assets/CloudRunButtonWrapper-DRsRqjzK.js (removed) 1.76 kB 🟢 -1.76 kB 🟢 -812 B 🟢 -725 B
assets/CloudRunButtonWrapper-DsrAFSJL.js (new) 1.76 kB 🔴 +1.76 kB 🔴 +815 B 🔴 +721 B
assets/cloudBadges-COiGSsuF.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -789 B 🟢 -703 B
assets/cloudBadges-CUaapJrz.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +790 B 🔴 +700 B
assets/previousFullPath-a94rK3-B.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +695 B 🔴 +600 B
assets/previousFullPath-BHHgPU9m.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -694 B 🟢 -600 B
assets/cloudSubscription-EsW3WxNR.js (removed) 1.45 kB 🟢 -1.45 kB 🟢 -708 B 🟢 -603 B
assets/cloudSubscription-Frijo7V_.js (new) 1.45 kB 🔴 +1.45 kB 🔴 +710 B 🔴 +607 B
assets/Load3D-CiaAs8UH.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +501 B 🔴 +444 B
assets/Load3D-D3d0NuCB.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -497 B 🟢 -440 B
assets/nightlyBadges-CJnVEm0b.js (removed) 1.06 kB 🟢 -1.06 kB 🟢 -555 B 🟢 -492 B
assets/nightlyBadges-DMJg9udW.js (new) 1.06 kB 🔴 +1.06 kB 🔴 +558 B 🔴 +497 B
assets/Load3dViewerContent-BgJTDeNd.js (new) 993 B 🔴 +993 B 🔴 +470 B 🔴 +420 B
assets/Load3dViewerContent-Bmb2Oz3n.js (removed) 993 B 🟢 -993 B 🟢 -467 B 🟢 -417 B
assets/SubscriptionPanelContentWorkspace-CDvk7p8X.js (new) 920 B 🔴 +920 B 🔴 +444 B 🔴 +378 B
assets/SubscriptionPanelContentWorkspace-tRrJCMHS.js (removed) 920 B 🟢 -920 B 🟢 -440 B 🟢 -380 B
assets/graphHasMissingNodes-CcVnju7K.js (new) 822 B 🔴 +822 B 🔴 +416 B 🔴 +347 B
assets/graphHasMissingNodes-jqvMwbXO.js (removed) 822 B 🟢 -822 B 🟢 -414 B 🟢 -347 B
assets/WidgetLegacy-Bzpq5nNO.js (new) 744 B 🔴 +744 B 🔴 +389 B 🔴 +340 B
assets/WidgetLegacy-CcTsvRML.js (removed) 744 B 🟢 -744 B 🟢 -386 B 🟢 -341 B
assets/changeTracker-BlB4fVgX.js (new) 720 B 🔴 +720 B 🔴 +378 B 🔴 +327 B
assets/changeTracker-CRCtTHwL.js (removed) 720 B 🟢 -720 B 🟢 -371 B 🟢 -324 B
assets/AnimationControls-D_ssPKpl.js 4.78 kB 4.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-CD-599s-.js 2.86 kB 2.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-Da_dLKSr.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-BO4bu8SW.js 1.92 kB 1.92 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single--m8ho-x0.js 272 B 272 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-B5_02-pL.js 960 B 960 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-6C7uscef.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-aHWGoXwC.js 15.3 kB 15.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B0dAi43J.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bm9EUr3J.js 17.2 kB 17.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bp4kggpz.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CfrqwZHT.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CWNMjnpx.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D6U7YFhN.js 17.8 kB 17.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DC5sgzz0.js 19.1 kB 19.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dm1AG3mI.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-pcbFneET.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-CDJrbKEX.js 766 B 766 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-CwuQK4UY.js 551 kB 551 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-On41i07W.js 137 B 137 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Loader-D9NTeFNU.js 1.26 kB 1.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-9XrHf480.js 161 kB 161 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-B9TJl5aV.js 194 kB 194 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BzT1W2Nf.js 186 kB 186 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CFS-ULTo.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CQYQjN9d.js 164 kB 164 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DhzCBjQp.js 170 kB 170 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Do-A6RrX.js 227 kB 227 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DUG-f7Fv.js 203 kB 203 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-J2KYVTYr.js 162 kB 162 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-PYENluGE.js 167 kB 167 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-TpUKvgkW.js 142 kB 142 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-lR1JiQqq.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DDwe88_i.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-B3Ndzn8y.js 2.02 kB 2.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-DLyNtqwJ.js 1.07 kB 1.07 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-BPdj4Cu9.js 1.06 kB 1.06 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-DKoWz3mq.js 2.94 kB 2.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/NightlySurveyController-BJ7Yfxt0.js 9.5 kB 9.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-6UQkLW3t.js 397 kB 397 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-B6cTYNTY.js 422 kB 422 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BedoymkZ.js 402 kB 402 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Byb68g74.js 410 kB 410 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CZwYcETe.js 457 kB 457 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DE3N6WlW.js 405 kB 405 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DjUwyPyw.js 368 kB 368 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DsemVF5Q.js 372 kB 372 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-lDfYFWDc.js 499 kB 499 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-PhTfraW0.js 457 kB 457 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-YWVW2Rh0.js 406 kB 406 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-xbED3XYT.js 3.77 kB 3.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DBfy44LZ.js 2.02 kB 2.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-BkB8Nehs.js 9.8 kB 9.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-BMWtuepD.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-BYG1u0oC.js 3.57 kB 3.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-C5oPOZUH.js 290 B 290 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-CecH6qXS.js 2.28 kB 2.28 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-7ZMuZPoG.js 443 B 443 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Textarea-BCdbAQYh.js 1.42 kB 1.42 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-BqIM6TDt.js 313 B 313 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-BEMiTJfJ.js 1.51 kB 1.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DloL8--t.js 3.5 kB 3.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-BR7_6Tzc.js 3.62 kB 3.62 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-C7hcUvUl.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-Br2tE61m.js 2.41 kB 2.41 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-7LajmWlG.js 16.2 kB 16.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-BP4f8SEB.js 3.8 kB 3.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-DRrbKltR.js 7.78 kB 7.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-DZD-PABh.js 3.09 kB 3.09 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-DQtrhTV6.js 2.37 kB 2.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-CifbFnvo.js 3.13 kB 3.13 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-U-tRpxOF.js 1.52 kB 1.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-CMqqTQxu.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-ju3asrao.js 416 B 416 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 52 added / 52 removed

@github-actions
Copy link

github-actions bot commented Mar 13, 2026

⚡ Performance Report

ℹ️ Collecting baseline variance data (0/5 runs). Significance will appear after 2 main branch runs.

Metric Baseline PR Δ
canvas-idle: style recalcs 13 12 -8%
canvas-idle: layouts 1 0 -50%
canvas-idle: task duration 354ms 365ms +3%
canvas-mouse-sweep: style recalcs 76 81 +6%
canvas-mouse-sweep: layouts 12 12 +0%
canvas-mouse-sweep: task duration 811ms 958ms +18%
dom-widget-clipping: style recalcs 14 13 -2%
dom-widget-clipping: layouts 0 0 +0%
dom-widget-clipping: task duration 341ms 348ms +2%
subgraph-dom-widget-clipping: style recalcs 50 49 -3%
subgraph-dom-widget-clipping: layouts 0 0 -100%
subgraph-dom-widget-clipping: task duration 373ms 355ms -5%
subgraph-idle: style recalcs 12 12 -5%
subgraph-idle: layouts 0 0 +0%
subgraph-idle: task duration 334ms 376ms +13%
subgraph-mouse-sweep: style recalcs 81 82 +1%
subgraph-mouse-sweep: layouts 16 16 +2%
subgraph-mouse-sweep: task duration 741ms 744ms +0%
Raw data
{
  "timestamp": "2026-03-14T08:31:20.983Z",
  "gitSha": "3b12301c6d6c2e83446590c3f1744d5cc8374ccb",
  "branch": "drjkl/fix/subgraph-widgets",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2046.9489999999837,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 14.616999999999997,
      "layouts": 1,
      "layoutDurationMs": 0.3070000000000001,
      "taskDurationMs": 372.166,
      "heapDeltaBytes": 1414804
    },
    {
      "name": "canvas-idle",
      "durationMs": 2037.113999999974,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 13.059999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 370.891,
      "heapDeltaBytes": 851504
    },
    {
      "name": "canvas-idle",
      "durationMs": 2020.390999999961,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.482999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 350.95300000000003,
      "heapDeltaBytes": -6008364
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2108.7589999999636,
      "styleRecalcs": 85,
      "styleRecalcDurationMs": 51.037000000000006,
      "layouts": 12,
      "layoutDurationMs": 3.828,
      "taskDurationMs": 1112.489,
      "heapDeltaBytes": -2370760
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1780.2770000000123,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 37.693,
      "layouts": 12,
      "layoutDurationMs": 3.45,
      "taskDurationMs": 783.701,
      "heapDeltaBytes": -2828240
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2029.42900000005,
      "styleRecalcs": 84,
      "styleRecalcDurationMs": 44.053000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.5090000000000003,
      "taskDurationMs": 978.9079999999999,
      "heapDeltaBytes": -3446472
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 556.5619999999853,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 9.514,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 344.27799999999996,
      "heapDeltaBytes": 10761080
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 569.0940000000069,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 8.690999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 351.279,
      "heapDeltaBytes": 12564460
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 563.9620000000036,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 9.431999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 347.50899999999996,
      "heapDeltaBytes": 12727016
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 553.7889999999948,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 13.054000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 348.0489999999999,
      "heapDeltaBytes": 12557392
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 556.5530000000081,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 12.376999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 349.08399999999995,
      "heapDeltaBytes": 12927456
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 593.9649999999688,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 12.736,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 368.65799999999996,
      "heapDeltaBytes": 12626668
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2015.7600000000002,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.067,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 419.06100000000004,
      "heapDeltaBytes": 872164
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1993.0309999999736,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 10.68,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 359.23,
      "heapDeltaBytes": 54996
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2009.7240000000056,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 11.234,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 349.9,
      "heapDeltaBytes": 305292
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1670.7450000000108,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 38.99100000000001,
      "layouts": 16,
      "layoutDurationMs": 5.088,
      "taskDurationMs": 663.2679999999999,
      "heapDeltaBytes": -7841908
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2022.153000000003,
      "styleRecalcs": 90,
      "styleRecalcDurationMs": 50.518,
      "layouts": 17,
      "layoutDurationMs": 4.667999999999999,
      "taskDurationMs": 913.895,
      "heapDeltaBytes": -6986120
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1714.334000000008,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 35.574,
      "layouts": 16,
      "layoutDurationMs": 4.1659999999999995,
      "taskDurationMs": 654.842,
      "heapDeltaBytes": -6399068
    }
  ]
}

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

Caution

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

⚠️ Outside diff range comments (1)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

369-389: ⚠️ Potential issue | 🟠 Major

Do not treat unresolved fallback promotions as stale during hydration.

When every input has a linked representative, _shouldPersistLinkedOnly() returns true even if some fallback entries are only temporarily unresolved. _syncPromotions() then drops those entries from the store, so an independent promotion that has not rebound yet can be lost permanently during configure/hydration. This should only prune fallbacks that are positively proven to alias one of the linked entries.

Also applies to: 424-440

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

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts` around lines 369 - 389, The
current logic in _buildPromotionPersistenceState (and the similar block around
lines 424-440) calls _shouldPersistLinkedOnly and drops fallback entries
whenever every input has a linked representative, which incorrectly prunes
temporarily unresolved fallbacks; change the logic so we only persist
linked-only when each fallbackStoredEntry is positively proven to alias a
linkedPromotionEntry (i.e., modify _shouldPersistLinkedOnly to validate aliasing
between fallbackStoredEntries and linkedPromotionEntries, or alternatively
adjust _buildPromotionPersistenceState to include fallbackStoredEntries unless
alias validation passes), and ensure _syncPromotions uses this stricter decision
so independent promotions that are unresolved are not removed during
hydration/configure; reference functions: _buildPromotionPersistenceState,
_collectLinkedAndFallbackEntries, _shouldPersistLinkedOnly, and _syncPromotions.
🧹 Nitpick comments (1)
src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1)

279-283: Drive visibility and dedupe from the merged widget options.

isWidgetVisible() and the stored hidden/advanced flags still read widget.options, but the rendered widget uses widgetState?.options through widgetOptions. If store state overrides visibility flags, the new dedupe logic can still keep the stale copy or reserve rows for a widget that will not render. Pulling visibility from the same merged options object would keep these paths consistent.

Also applies to: 350-356, 401-406, 423-429

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

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue` around lines 279
- 283, isWidgetVisible and other places currently read visibility flags from the
original SafeWidgetData.options, but the UI renders using the merged
widgetOptions (from widgetState?.options), causing stale visibility/dedupe
logic; change isWidgetVisible (and the references at the other occurrences) to
read hidden/advanced from the merged widgetOptions used for rendering (e.g., use
widgetOptions or compute mergedOptions = { ...widget.options,
...widgetState?.options } and then derive const hidden = mergedOptions.hidden ??
false and advanced = mergedOptions.advanced ?? false) so visibility and dedupe
use the same source as the rendered widget.
🤖 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/parameters/SectionWidgets.vue`:
- Around line 96-105: getWidgetRenderKey currently includes runtime
widgetNode.id which changes when a node transitions from temporary (-1) to
configured, causing remounts; update getWidgetRenderKey so both promoted and
non-promoted keys use a stable, per-view identity instead of widgetNode.id (e.g.
use a persisted/stable id property on the widget or its view such as
widget.viewId or widget.stableId, or fallback to widget.sourceNodeId for
promoted widgets) — change the promoted branch (inside isPromotedWidgetView) and
the fallback branch to build keys from stable identifiers
(widget.sourceViewId/widget.sourceNodeId/widget.stableId and
widget.name/widget.type) rather than widgetNode.id so items do not remount on id
transitions.

In `@src/composables/graph/useGraphNodeManager.ts`:
- Around line 245-261: The current matchedInput search can pick a same-name
input before the one actually bound to this promoted widget; change the lookup
to prefer an exact widget binding first: find an input where input._widget ===
widget, and only if that returns undefined fall back to finding by input.name
=== widget.name; update the matchedInput assignment in useGraphNodeManager (the
variable named matchedInput that currently uses node.inputs?.find(...)) and keep
the rest of the logic using promotedInputName, displayName, and
resolvePromotedSourceByInputName unchanged.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 263-313: Promoted views stay cached across input renames because
signatures use inputKey only, so update the cache/signature logic to include the
input's display name (or widget name) to invalidate on rename: modify
_makePromotedEntriesSignature and _makeLinkedEntriesSignature to incorporate the
input display name (e.g., entry.inputName or entry.widgetName as appropriate)
when building signatures, and ensure
_writePromotedViewsCache/_readPromotedViewsCache use those updated signatures;
alternatively, make PromotedWidgetView.displayName mutable and update it during
reconcile so renames are reflected even when the cached PromotedWidgetView
instance is reused (refer to PromotedWidgetView, PromotionEntry,
LinkedPromotionEntry, _readPromotedViewsCache, _writePromotedViewsCache,
_makePromotedEntriesSignature, and _makeLinkedEntriesSignature).
- Around line 823-877: The serialize path still matches promoted inputs to
subgraph inputs by name, so duplicate names collide; update the
SubgraphNode.serialize (and the analogous code at the second location
referenced) to persist the stable slot identity using input._subgraphSlot (e.g.,
serialize the slot id or another stable identifier) instead of input.name, and
update deserialization to look up the slot by that id and reassign
input._subgraphSlot accordingly; ensure you handle missing/undefined
_subgraphSlot by falling back to the existing name-based lookup so older saves
remain compatible.

---

Outside diff comments:
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 369-389: The current logic in _buildPromotionPersistenceState (and
the similar block around lines 424-440) calls _shouldPersistLinkedOnly and drops
fallback entries whenever every input has a linked representative, which
incorrectly prunes temporarily unresolved fallbacks; change the logic so we only
persist linked-only when each fallbackStoredEntry is positively proven to alias
a linkedPromotionEntry (i.e., modify _shouldPersistLinkedOnly to validate
aliasing between fallbackStoredEntries and linkedPromotionEntries, or
alternatively adjust _buildPromotionPersistenceState to include
fallbackStoredEntries unless alias validation passes), and ensure
_syncPromotions uses this stricter decision so independent promotions that are
unresolved are not removed during hydration/configure; reference functions:
_buildPromotionPersistenceState, _collectLinkedAndFallbackEntries,
_shouldPersistLinkedOnly, and _syncPromotions.

---

Nitpick comments:
In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue`:
- Around line 279-283: isWidgetVisible and other places currently read
visibility flags from the original SafeWidgetData.options, but the UI renders
using the merged widgetOptions (from widgetState?.options), causing stale
visibility/dedupe logic; change isWidgetVisible (and the references at the other
occurrences) to read hidden/advanced from the merged widgetOptions used for
rendering (e.g., use widgetOptions or compute mergedOptions = {
...widget.options, ...widgetState?.options } and then derive const hidden =
mergedOptions.hidden ?? false and advanced = mergedOptions.advanced ?? false) so
visibility and dedupe use the same source as the rendered widget.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 994d5c92-643b-4820-b540-dae4f100ae1c

📥 Commits

Reviewing files that changed from the base of the PR and between 82556f0 and 6ab7cd6.

📒 Files selected for processing (13)
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/composables/graph/useGraphNodeManager.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphComplexPromotion1.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts
  • src/stores/promotionStore.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1)

398-407: ⚠️ Potential issue | 🟠 Major

AppInput id is no longer unique per widget.

id: bareWidgetId collides for all widgets on the same node. This can break input-label association and widget-level targeting.

💡 Suggested fix
-      id: bareWidgetId,
+      id: `${bareWidgetId}:${widget.slotName ?? widget.name}`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue` around lines 398
- 407, The id assigned to each widget in the object pushed into result (id:
bareWidgetId) is not unique across widgets on the same node; change the id
generation to include a widget-unique suffix (e.g., combine bareWidgetId with
renderKey, widget.id, or the widget index) so each widget id is unique and
preserves input–label associations and per-widget targeting; update the object
created in the result.push call (the id property) to concatenate bareWidgetId
with the chosen unique token (renderKey/widget.id/index) consistently wherever
those ids are consumed.
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

593-599: ⚠️ Potential issue | 🔴 Critical

Guard the input-added existing-input path against missing links.

This branch can throw when subgraphInput.linkIds[0] is absent/stale (subgraph.links[linkId] is undefined), which breaks subgraph input reconciliation at runtime.

Proposed fix
         if (existingInput) {
-          const linkId = subgraphInput.linkIds[0]
-          const { inputNode, input } = subgraph.links[linkId].resolve(subgraph)
+          const linkId = subgraphInput.linkIds[0]
+          if (linkId === undefined) return
+
+          const link = this.subgraph.getLink(linkId)
+          if (!link) return
+
+          const { inputNode, input } = link.resolve(subgraph)
           const widget = inputNode?.widgets?.find?.((w) => w.name === name)
           if (widget && inputNode)
             this._setWidget(
               subgraphInput,
               existingInput,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts` around lines 593 - 599, In
the existing-input path of SubgraphNode where you use subgraphInput.linkIds[0]
and access subgraph.links[linkId].resolve(subgraph), add guards to ensure
linkIds[0] exists and subgraph.links[linkId] is defined before calling resolve;
if either is missing, skip reconciliation for that existingInput (e.g.,
return/continue or fall back to a safe no-op) and ensure you also check that
resolve(...) returns a non-null object before accessing inputNode or input and
before reading inputNode.widgets; this prevents runtime exceptions when link
metadata is stale.
♻️ Duplicate comments (2)
src/composables/graph/useGraphNodeManager.ts (1)

245-259: ⚠️ Potential issue | 🟠 Major

Prefer exact _widget slot match before name fallback.

Name-first matching can still select the wrong same-name slot and produce incorrect promoted-source mapping.

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

In `@src/composables/graph/useGraphNodeManager.ts` around lines 245 - 259, The
current matching picks an input by name first which can choose the wrong
same-name slot; change the lookup to prefer an exact slot identity match
(input._widget === widget) before falling back to name equality: in the logic
around matchedInput (and consequently promotedInputName/displayName and
promotedSource) first search node.inputs for input._widget === widget, if none
found then search for input.name === widget.name, then compute promotedSource
using resolvePromotedSourceByInputName(displayName) only when the matched input
was the exact widget match; otherwise use directSource.
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

1113-1125: ⚠️ Potential issue | 🟠 Major

Serialize widget-value sync via _subgraphSlot identity, not slot name.

Runtime mapping is now identity-based, but serialization still resolves by slot.name === input.name. Duplicate-name inputs can still sync to the wrong subgraph slot before save.

Proposed fix
-      const subgraphInput = this.subgraph.inputNode.slots.find(
-        (slot) => slot.name === input.name
-      )
+      const subgraphInput =
+        input._subgraphSlot ??
+        this.subgraph.inputNode.slots.find(
+          (slot) => slot.name === input.name
+        )
       if (!subgraphInput) continue

Also applies to: 1389-1391

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

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts` around lines 1113 - 1125, The
serialization currently matches subgraph slots by slot.name causing
duplicate-named inputs to mis-map; update the logic in getSlotFromWidget and
getWidgetFromSlot to use the slot identity/property used at runtime (the
`_subgraphSlot` identity or the actual slot object) instead of comparing
names—specifically, when resolving inputs in getSlotFromWidget use
input._subgraphSlot (or strict object equality to `widget._subgraphSlot`) to
find the matching input, and when resolving widgets in getWidgetFromSlot ensure
you return the widget bound to the exact input._subgraphSlot identity; apply the
same identity-based change to the corresponding code around the other occurrence
(lines referencing similar logic at the 1389–1391 area).
🤖 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/core/graph/subgraph/promotedWidgetView.ts`:
- Around line 136-159: The current early return when getLinkedInputWidgets()
yields any linked widgets can drop writes if widgetStore entries aren't yet
ready; modify the block using useWidgetValueStore() so you track whether any
state was actually updated (e.g., a boolean updated = false), set updated = true
when you assign state.value for linked widgets or resolvedState for
resolveDeepest(), and only return early if updated is true; otherwise fall
through to the normal set logic so set value isn't a no-op when stores are not
yet hydrated (refer to getLinkedInputWidgets, useWidgetValueStore,
resolveDeepest, stripGraphPrefix, graphId in your changes).

---

Outside diff comments:
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 593-599: In the existing-input path of SubgraphNode where you use
subgraphInput.linkIds[0] and access subgraph.links[linkId].resolve(subgraph),
add guards to ensure linkIds[0] exists and subgraph.links[linkId] is defined
before calling resolve; if either is missing, skip reconciliation for that
existingInput (e.g., return/continue or fall back to a safe no-op) and ensure
you also check that resolve(...) returns a non-null object before accessing
inputNode or input and before reading inputNode.widgets; this prevents runtime
exceptions when link metadata is stale.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue`:
- Around line 398-407: The id assigned to each widget in the object pushed into
result (id: bareWidgetId) is not unique across widgets on the same node; change
the id generation to include a widget-unique suffix (e.g., combine bareWidgetId
with renderKey, widget.id, or the widget index) so each widget id is unique and
preserves input–label associations and per-widget targeting; update the object
created in the result.push call (the id property) to concatenate bareWidgetId
with the chosen unique token (renderKey/widget.id/index) consistently wherever
those ids are consumed.

---

Duplicate comments:
In `@src/composables/graph/useGraphNodeManager.ts`:
- Around line 245-259: The current matching picks an input by name first which
can choose the wrong same-name slot; change the lookup to prefer an exact slot
identity match (input._widget === widget) before falling back to name equality:
in the logic around matchedInput (and consequently promotedInputName/displayName
and promotedSource) first search node.inputs for input._widget === widget, if
none found then search for input.name === widget.name, then compute
promotedSource using resolvePromotedSourceByInputName(displayName) only when the
matched input was the exact widget match; otherwise use directSource.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 1113-1125: The serialization currently matches subgraph slots by
slot.name causing duplicate-named inputs to mis-map; update the logic in
getSlotFromWidget and getWidgetFromSlot to use the slot identity/property used
at runtime (the `_subgraphSlot` identity or the actual slot object) instead of
comparing names—specifically, when resolving inputs in getSlotFromWidget use
input._subgraphSlot (or strict object equality to `widget._subgraphSlot`) to
find the matching input, and when resolving widgets in getWidgetFromSlot ensure
you return the widget bound to the exact input._subgraphSlot identity; apply the
same identity-based change to the corresponding code around the other occurrence
(lines referencing similar logic at the 1389–1391 area).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f240ebdd-6104-446d-8567-0e0bf577c7fa

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab7cd6 and 4772eb4.

📒 Files selected for processing (8)
  • src/composables/graph/useGraphNodeManager.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/core/graph/subgraph/widgetNodeTypeGuard.ts
  • src/lib/litegraph/src/strings.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue

@DrJKL DrJKL marked this pull request as ready for review March 13, 2026 21:59
@DrJKL DrJKL requested a review from a team as a code owner March 13, 2026 21:59
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 13, 2026
@DrJKL DrJKL requested a review from LittleSound March 13, 2026 22:00
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: 4

Caution

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

⚠️ Outside diff range comments (1)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

1299-1320: ⚠️ Potential issue | 🟠 Major

View-key cleanup uses a mismatched key shape and can leave stale aliases.

Line 1299 builds a removal key without inputName, but creation paths include it. Line 1315 also passes view.name as inputKey, which does not match the current key contract. This can leave stale viewKey aliases in the manager and cause incorrect reuse.

Proposed fix
     for (const input of this.inputs) {
       if (input._widget !== view || !input._subgraphSlot) continue
+      const inputName = input.label ?? input.name

       this._promotedViewManager.removeByViewKey(
         view.sourceNodeId,
         view.sourceWidgetName,
         this._makePromotionViewKey(
           String(input._subgraphSlot.id),
           view.sourceNodeId,
-          view.sourceWidgetName
+          view.sourceWidgetName,
+          inputName
         )
       )
     }

-    // Reconciled views can also be keyed by inputName-scoped view keys.
-    // Remove both key shapes to avoid stale cache entries across promote/rebind flows.
-    this._promotedViewManager.removeByViewKey(
-      view.sourceNodeId,
-      view.sourceWidgetName,
-      this._makePromotionViewKey(
-        view.name,
-        view.sourceNodeId,
-        view.sourceWidgetName
-      )
-    )
+    // If legacy key cleanup is still needed, remove using the exact legacy format
+    // expected by PromotedWidgetViewManager (not _makePromotionViewKey).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts` around lines 1299 - 1320, The
removal calls to _promotedViewManager.removeByViewKey use the wrong key shape
compared to creation: replace the mismatched arguments so both removals call
_makePromotionViewKey with the same input identifier used at creation (the
subgraph input name/id), e.g. use String(input._subgraphSlot.id) (or the
canonical inputName variable) instead of view.name in the second call and ensure
the first call also includes the inputName-shaped key; update the two
_promotedViewManager.removeByViewKey invocations that reference
_makePromotionViewKey so they pass the identical input key format used when
views were promoted (matching the creation path).
🤖 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/core/graph/subgraph/promotedWidgetView.test.ts`:
- Around line 1533-1566: The test currently allows 0 resolver calls; change the
final assertion to ensure the resolver actually runs once on the first access
and is not re-run on the second by: call subgraphNode.widgets once, assert
resolveSpy.mock.calls.length === 1, then access subgraphNode.widgets a second
time and assert resolveSpy.mock.calls.length remains 1; reference the spy on
_resolveLinkedPromotionBySubgraphInput and the two uses of subgraphNode.widgets
to implement these two assertions.

In `@src/core/graph/subgraph/promotedWidgetView.ts`:
- Around line 327-336: getLinkedInputWidgets() is matching inputs by raw
`_widget === this`, which fails during configure/hydration; replace that check
with a call to matchPromotedInput(this, input) while still restricting to inputs
that have `_subgraphSlot`. Locate the method getLinkedInputWidgets and change
the inputs.find predicate to use matchPromotedInput(this, input) (or equivalent)
instead of `_widget === this`, ensuring you still only consider inputs where
`_subgraphSlot` is truthy so the function continues to return linked promoted
inputs for getWidgetState() and the value setter during rebinding.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 624-626: The current existing-input path uses a name-based lookup
(inputNode?.widgets?.find(w => w.name === name)) which can select the wrong
widget when names duplicate; replace that lookup by using the resolved slot
object `input` returned from `link.resolve(subgraph)` and call the utility
`getWidgetFromSlot(inputNode, input)` (or the equivalent helper in this module)
to retrieve the widget by slot identity; update the logic around `const {
inputNode, input } = link.resolve(subgraph)` to use `getWidgetFromSlot` instead
of the `.find` by name so the widget selection is slot-safe.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue`:
- Around line 242-269: The dedupeIdentity in getWidgetIdentity currently calls
stripGraphPrefix(rawWidgetId) which collapses scoped ids (e.g. "65:19" and
"66:19") and causes distinct promoted widgets to merge; update getWidgetIdentity
to use the full rawWidgetId (or include widget.sourceExecutionId) when building
stableIdentityRoot instead of stripGraphPrefix(rawWidgetId), ensuring
dedupeIdentity preserves scope; ensure renderKey logic remains consistent and
add a regression test that creates two widgets with scoped ids like "65:19" and
"66:19" to verify they produce distinct dedupeIdentity/renderKey values.

---

Outside diff comments:
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 1299-1320: The removal calls to
_promotedViewManager.removeByViewKey use the wrong key shape compared to
creation: replace the mismatched arguments so both removals call
_makePromotionViewKey with the same input identifier used at creation (the
subgraph input name/id), e.g. use String(input._subgraphSlot.id) (or the
canonical inputName variable) instead of view.name in the second call and ensure
the first call also includes the inputName-shaped key; update the two
_promotedViewManager.removeByViewKey invocations that reference
_makePromotionViewKey so they pass the identical input key format used when
views were promoted (matching the creation path).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32a3d62a-356b-42af-8473-fd515269104b

📥 Commits

Reviewing files that changed from the base of the PR and between 4772eb4 and 6ab59e9.

📒 Files selected for processing (12)
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/composables/graph/useGraphNodeManager.test.ts
  • src/composables/graph/useGraphNodeManager.ts
  • src/core/graph/subgraph/matchPromotedInput.test.ts
  • src/core/graph/subgraph/matchPromotedInput.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/core/graph/subgraph/widgetRenderKey.test.ts
  • src/core/graph/subgraph/widgetRenderKey.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/composables/graph/useGraphNodeManager.ts
  • src/composables/graph/useGraphNodeManager.test.ts

Comment on lines +1533 to +1566
test('does not re-resolve linked entries when linked input state is unchanged', () => {
const subgraph = createTestSubgraph({
inputs: [{ name: 'string_a', type: '*' }]
})
const subgraphNode = createTestSubgraphNode(subgraph, { id: 97 })
subgraphNode.graph?.add(subgraphNode)

const linkedNodeA = new LGraphNode('LinkedNodeA')
const linkedInputA = linkedNodeA.addInput('string_a', '*')
linkedNodeA.addWidget('text', 'string_a', 'a', () => {})
linkedInputA.widget = { name: 'string_a' }
subgraph.add(linkedNodeA)

const linkedNodeB = new LGraphNode('LinkedNodeB')
const linkedInputB = linkedNodeB.addInput('string_a', '*')
linkedNodeB.addWidget('text', 'string_a', 'b', () => {})
linkedInputB.widget = { name: 'string_a' }
subgraph.add(linkedNodeB)

subgraph.inputNode.slots[0].connect(linkedInputA, linkedNodeA)
subgraph.inputNode.slots[0].connect(linkedInputB, linkedNodeB)

const resolveSpy = vi.spyOn(
subgraphNode as unknown as {
_resolveLinkedPromotionBySubgraphInput: (...args: unknown[]) => unknown
},
'_resolveLinkedPromotionBySubgraphInput'
)

void subgraphNode.widgets
void subgraphNode.widgets

expect(resolveSpy.mock.calls.length).toBeLessThanOrEqual(1)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tighten the resolver call-count assertion to avoid false positives.

Line 1565 currently allows 0 calls, so this test can pass even if linked resolution never runs. Assert one call after first read, then unchanged after second read.

🔧 Suggested test assertion tightening
-    void subgraphNode.widgets
-    void subgraphNode.widgets
-
-    expect(resolveSpy.mock.calls.length).toBeLessThanOrEqual(1)
+    void subgraphNode.widgets
+    expect(resolveSpy).toHaveBeenCalledTimes(1)
+    void subgraphNode.widgets
+    expect(resolveSpy).toHaveBeenCalledTimes(1)

As per coding guidelines: "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."

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

In `@src/core/graph/subgraph/promotedWidgetView.test.ts` around lines 1533 - 1566,
The test currently allows 0 resolver calls; change the final assertion to ensure
the resolver actually runs once on the first access and is not re-run on the
second by: call subgraphNode.widgets once, assert resolveSpy.mock.calls.length
=== 1, then access subgraphNode.widgets a second time and assert
resolveSpy.mock.calls.length remains 1; reference the spy on
_resolveLinkedPromotionBySubgraphInput and the two uses of subgraphNode.widgets
to implement these two assertions.

Comment on lines +242 to +269
function getWidgetIdentity(
widget: SafeWidgetData,
nodeId: string | number | undefined,
index: number
): {
dedupeIdentity?: string
renderKey: string
} {
const rawWidgetId = widget.storeNodeId ?? widget.nodeId
const storeWidgetName = widget.storeName ?? widget.name
const slotNameForIdentity = widget.slotName ?? widget.name
const stableIdentityRoot = rawWidgetId
? `node:${String(stripGraphPrefix(rawWidgetId))}`
: widget.sourceExecutionId
? `exec:${widget.sourceExecutionId}`
: undefined

const dedupeIdentity = stableIdentityRoot
? `${stableIdentityRoot}:${storeWidgetName}:${slotNameForIdentity}:${widget.type}`
: undefined
const renderKey =
dedupeIdentity ??
`transient:${String(nodeId ?? '')}:${storeWidgetName}:${slotNameForIdentity}:${widget.type}:${index}`

return {
dedupeIdentity,
renderKey
}
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't strip the scoped source id out of the dedupe identity.

stripGraphPrefix() turns scoped ids like 65:19 and 66:19 into the same node:19. If two promoted widgets share local node id/name/type but come from different subgraph execution paths, uniqueWidgets collapses them into one row and one renderKey, hiding a legitimate widget. Keep the full scoped id here—or append sourceExecutionId—and leave stripGraphPrefix() only in the widget store lookups below. Please add a regression with ids like 65:19 and 66:19.

💡 Possible fix
-  const stableIdentityRoot = rawWidgetId
-    ? `node:${String(stripGraphPrefix(rawWidgetId))}`
-    : widget.sourceExecutionId
-      ? `exec:${widget.sourceExecutionId}`
-      : undefined
+  const stableIdentityRoot = widget.sourceExecutionId
+    ? `exec:${widget.sourceExecutionId}`
+    : rawWidgetId
+      ? `node:${String(rawWidgetId)}`
+      : undefined
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue` around lines 242
- 269, The dedupeIdentity in getWidgetIdentity currently calls
stripGraphPrefix(rawWidgetId) which collapses scoped ids (e.g. "65:19" and
"66:19") and causes distinct promoted widgets to merge; update getWidgetIdentity
to use the full rawWidgetId (or include widget.sourceExecutionId) when building
stableIdentityRoot instead of stripGraphPrefix(rawWidgetId), ensuring
dedupeIdentity preserves scope; ensure renderKey logic remains consistent and
add a regression test that creates two widgets with scoped ids like "65:19" and
"66:19" to verify they produce distinct dedupeIdentity/renderKey values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The nodes should all have unique IDs.

Copy link
Contributor

Choose a reason for hiding this comment

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

@DrJKL, understood — if node IDs are guaranteed to be unique across the graph (including across subgraph execution paths), then stripGraphPrefix can safely be used in the dedup key without risk of collapsing distinct nodes. My concern doesn't apply here. Thanks for the clarification!


✏️ Learnings added
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 9896
File: src/renderer/extensions/vueNodes/components/NodeWidgets.vue:242-269
Timestamp: 2026-03-14T01:15:36.437Z
Learning: In Comfy-Org/ComfyUI_frontend, LGraph node IDs are globally unique across all subgraph scopes and execution paths. Therefore, calling `stripGraphPrefix()` on a scoped id (e.g. "65:19" → "19") in deduplication/identity keys inside NodeWidgets.vue is safe and will not collide with IDs from other nodes, even those under different graph prefixes. Do not flag stripGraphPrefix usage in node-ID-based dedup keys as a collision risk.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 8594
File: src/lib/litegraph/src/widgets/BaseWidget.ts:146-155
Timestamp: 2026-02-06T09:27:31.576Z
Learning: In the ComfyUI frontend codebase: `LGraphNode.removeWidget()` (and the `widget.onRemove?.()` hook) is called during subgraph conversion operations where widgets are being moved or restructured, not just when they're being permanently deleted. Therefore, widget unregistration from `widgetValueStore` should not be tied to `onRemove`, as this would cause premature state loss during legitimate graph restructuring operations.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 9120
File: src/lib/litegraph/src/LGraph.ts:1600-1644
Timestamp: 2026-03-12T14:43:07.816Z
Learning: In Comfy-Org/ComfyUI_frontend (src/lib/litegraph/src/LGraph.ts), `_removeDuplicateLinks()` is intentionally scoped to freshly-deserialized data: it only runs during `configure()` (after `reroute.validateLinks()`) and `unpackSubgraph`. It does not need to handle live reroute state cleanup because reroutes are validated separately before this method executes. Do not suggest reroute `linkIds` cleanup inside `_removeDuplicateLinks`.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 8951
File: src/platform/workflow/management/stores/workflowStore.ts:301-301
Timestamp: 2026-03-05T23:21:47.113Z
Learning: In src/platform/workflow/management/stores/workflowStore.ts, `createTemporary()` and `createNewTemporary()` intentionally inject a UUID `id` into workflow data via `ensureWorkflowId()` to give workflows stable identity for sharing. This is a known behavioral change introduced in PR `#8951`. Extensions relying on serialized content matching their input may be silently affected.

Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 9894
File: src/lib/litegraph/src/LGraphCanvas.ts:0-0
Timestamp: 2026-03-14T00:14:35.308Z
Learning: In Comfy-Org/ComfyUI_frontend (src/lib/litegraph/src/LGraphCanvas.ts), _drawVueDragAlignmentGuides must only be invoked when LiteGraph.vueNodesMode is true. In drawFrontCanvas’s fallback path (used when overlayCtx is missing), keep the call gated to Vue mode so classic (non-Vue) rendering never shows Vue drag guides. It is correct to allow guides to render on the main canvas when overlayCtx is unavailable in Vue mode; do not gate on overlay availability.

Learnt from: dante01yoon
Repo: Comfy-Org/ComfyUI_frontend PR: 9712
File: src/renderer/extensions/vueNodes/widgets/components/DisplayCarousel.vue:61-64
Timestamp: 2026-03-12T08:28:20.395Z
Learning: In DisplayCarousel.vue (src/renderer/extensions/vueNodes/widgets/components/DisplayCarousel.vue), the "remove image" button (`handleRemove`) intentionally clears the entire node output (all images for the node), not just the currently active image. This mirrors the original PrimeVue/WidgetGalleria implementation behavior. Do not flag this as a bug during reviews; it is expected node-wide clearing behavior.

Learnt from: AustinMroz
Repo: Comfy-Org/ComfyUI_frontend PR: 9523
File: src/renderer/extensions/linearMode/PartnerNodesList.vue:23-33
Timestamp: 2026-03-07T21:51:45.520Z
Learning: In app-mode Vue components under src/renderer/extensions (e.g., PartnerNodesList.vue and similar components in ComfyUI_frontend), computed properties that traverse app.graph do not need a reactive dependency on workflowStore.activeWorkflow since nodes cannot be added or removed in app mode and the graph is effectively frozen. Do not flag missing reactive graph-change dependencies as an issue in app-mode components. Apply this guidance to similar files in the ComfyUI_frontend repository to avoid unnecessary reactivity checks on a frozen graph.

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: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:51.755Z
Learning: In Vue components under src/renderer/extensions/vueNodes (e.g., ImagePreview.vue and LGraphNode.vue), implement image gallery keyboard navigation so that it responds to the node's focus state rather than requiring focus inside the image preview wrapper. Achieve this by wiring keyEvent handling at the node focus level and injecting or propagating key events (arrow keys) to the gallery when the node is focused/selected. This improves accessibility and aligns navigation with node-level focus behavior.

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

Learnt from: Yourz
Repo: Comfy-Org/ComfyUI_frontend PR: 9085
File: src/components/sidebar/tabs/NodeLibrarySidebarTabV2.vue:45-50
Timestamp: 2026-02-27T09:39:47.145Z
Learning: In Vue components that use Reka UI primitives such as DropdownMenuTrigger and SelectTrigger with as-child, it is acceptable to render a raw <button> when the styling is context-specific and there is no existing Button variant to map to. The as-child pattern preserves accessibility while enabling composition. Do not create single-use Button variants for these cases; prefer inline styling with a raw button for clarity and to avoid unnecessary abstraction. This guideline applies to all Vue components (files ending in .vue) in projects using Reka UI.

Learnt from: jaeone94
Repo: Comfy-Org/ComfyUI_frontend PR: 9360
File: src/renderer/extensions/vueNodes/components/LGraphNode.vue:576-602
Timestamp: 2026-03-04T16:12:55.683Z
Learning: In Vue node components (e.g., LGraphNode.vue), keep computed properties that map RenderShape to CSS classes to explicitly handle only RenderShape.BOX and RenderShape.CARD, letting all other variants (ROUND, CIRCLE, HollowCircle, ARROW, GRID) fall through to a default rounded case. This reflects that only Default, Box, and Card shapes are user-selectable via the context menu; the other variants are internal for canvas rendering and should not be treated as selectable. Do not flag missing explicit cases as issues. Consider applying this defaulting pattern to similar node-shape components in the same Vue extension (src/renderer/extensions/vueNodes/components) to maintain consistency.

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 9427
File: src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuFilter.vue:33-33
Timestamp: 2026-03-06T00:53:28.835Z
Learning: When reviewing code, note that the enforce-canonical-classes (better-tailwindcss) rule may auto-fix Tailwind v3 !class-name syntax by converting it to v4 class-name! syntax. Do not treat these auto-fixed class-name! instances as newly introduced issues; the perceived change is in syntax placement, not in usage or intent. This guidance applies across all .vue and .ts files in the repository.

Learnt from: sonnybox
Repo: Comfy-Org/ComfyUI_frontend PR: 9446
File: src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue:45-45
Timestamp: 2026-03-06T01:55:00.013Z
Learning: Treat wrap-break-word as a valid Tailwind CSS utility for overflow-wrap: break-word in Tailwind v4+ projects. Do not flag this class as invalid in any Vue (.vue) or TypeScript (.ts/.tsx) files within the repository (e.g., Comfy-Org/ComfyUI_frontend) or other Tailwind v4+ projects. When reviewing, verify that the class is used to enable word breaking in long text content and reference the Tailwind docs: https://tailwindcss.com/docs/overflow-wrap.

Learnt from: benceruleanlu
Repo: Comfy-Org/ComfyUI_frontend PR: 9749
File: src/platform/assets/components/MediaAssetContextMenu.vue:88-93
Timestamp: 2026-03-11T19:12:43.621Z
Learning: For Vue components using PrimeVue ContextMenu, the root element merges all passthrough attributes via v-bind="ptmi('root')". Passing an id through :pt="{ root: { id: someId } }" will reliably set the root element's id, allowing document.getElementById(someId) to reference the overlay. Do not flag this pattern as unreliable in reviews for components using PrimeVue ContextMenu with pt.root.id passthrough (e.g., src/platform/assets/components/MediaAssetContextMenu.vue).

Learnt from: christian-byrne
Repo: Comfy-Org/ComfyUI_frontend PR: 9045
File: src/renderer/extensions/vueNodes/components/SlotContextMenu.vue:90-114
Timestamp: 2026-03-12T14:40:06.356Z
Learning: Do not wrap dialogService.prompt() calls in try/catch blocks in Vue components under src/renderer/extensions/vueNodes/components (e.g., SlotContextMenu.vue and similar). The dialogService handles its own errors internally; adding defensive try/catch provides no value and can clutter code. If a specific call site requires different handling, handle it at the call site with explicit error handling only when necessary.

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.

♻️ Duplicate comments (3)
src/core/graph/subgraph/promotedWidgetView.test.ts (1)

1562-1568: ⚠️ Potential issue | 🟡 Minor

Strengthen this resolver call-count assertion to avoid false positives.

This currently allows 0 calls, so the test can pass even if linked resolution never executes.

Proposed test tightening
-    void subgraphNode.widgets
-    const initialResolveCount = resolveSpy.mock.calls.length
-    expect(initialResolveCount).toBeLessThanOrEqual(1)
-
-    void subgraphNode.widgets
-    expect(resolveSpy).toHaveBeenCalledTimes(initialResolveCount)
+    void subgraphNode.widgets
+    expect(resolveSpy).toHaveBeenCalledTimes(1)
+
+    void subgraphNode.widgets
+    expect(resolveSpy).toHaveBeenCalledTimes(1)

As per coding guidelines: "Do not write tests that just test the mocks; ensure tests fail when code behaves unexpectedly."

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

In `@src/core/graph/subgraph/promotedWidgetView.test.ts` around lines 1562 - 1568,
The test currently allows zero resolver calls which can hide regressions; change
the assertion around resolveSpy and subgraphNode.widgets so the first access
actually triggered resolution and the second access is idempotent: after the
first read of subgraphNode.widgets capture resolveSpy.mock.calls.length and
assert it is at least 1 (e.g.
expect(initialResolveCount).toBeGreaterThanOrEqual(1) or equal 1) to ensure
resolution ran, then call void subgraphNode.widgets again and assert resolveSpy
was not incremented
(expect(resolveSpy).toHaveBeenCalledTimes(initialResolveCount)); use the
existing resolveSpy and subgraphNode.widgets identifiers when updating the test.
src/renderer/extensions/vueNodes/components/NodeWidgets.vue (1)

250-264: ⚠️ Potential issue | 🟠 Major

Keep scoped ids intact and ignore temporary -1 ids in the dedupe key.

stableIdentityRoot still prefers rawWidgetId and runs it through stripGraphPrefix(). That collapses distinct promoted widgets like 65:19 and 66:19 into the same identity, and it also treats hydration-time placeholders like -1 as stable, so entries can merge or remount when the real id arrives. Prefer widget.sourceExecutionId when it exists, otherwise use the full scoped rawWidgetId, and only use the transient fallback while the id is unresolved.

💡 Suggested fix
 function getWidgetIdentity(
   widget: SafeWidgetData,
   nodeId: string | number | undefined,
   index: number
 ): {
   dedupeIdentity?: string
   renderKey: string
 } {
   const rawWidgetId = widget.storeNodeId ?? widget.nodeId
+  const hasStableWidgetId =
+    rawWidgetId !== undefined &&
+    rawWidgetId !== null &&
+    String(rawWidgetId) !== '-1'
   const storeWidgetName = widget.storeName ?? widget.name
   const slotNameForIdentity = widget.slotName ?? widget.name
-  const stableIdentityRoot = rawWidgetId
-    ? `node:${String(stripGraphPrefix(rawWidgetId))}`
-    : widget.sourceExecutionId
-      ? `exec:${widget.sourceExecutionId}`
-      : undefined
+  const stableIdentityRoot = widget.sourceExecutionId
+    ? `exec:${widget.sourceExecutionId}`
+    : hasStableWidgetId
+      ? `node:${String(rawWidgetId)}`
+      : undefined
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue` around lines 250
- 264, stableIdentityRoot currently prefers rawWidgetId and runs
stripGraphPrefix on it which collapses scoped ids and treats placeholder -1 as
stable; change the logic in the stableIdentityRoot/dedupeIdentity/renderKey
computation to prefer widget.sourceExecutionId when present, otherwise use the
full scoped rawWidgetId (do not call stripGraphPrefix), and treat rawWidgetId
values that are unset or equal to "-1" as unresolved so the renderKey falls back
to the transient form; ensure dedupeIdentity still composes from
stableIdentityRoot, storeWidgetName, slotNameForIdentity, and widget.type when
stableIdentityRoot is defined.
src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts (1)

139-173: ⚠️ Potential issue | 🟡 Minor

These new identity tests still miss the broken collision path.

The "distinct promoted sources" case uses different local ids (...:19 vs ...:20), and the transient case uses undefined ids instead of the real -1 placeholder. The current getWidgetIdentity() bug still passes both. Please add a same-local-id/different-scope case (for example 65:19 vs 66:19) and a hydration case that starts at -1 and then resolves to a concrete id.

Also applies to: 231-258

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

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts` around lines
139 - 173, Update the test in NodeWidgets.test.ts to cover the missing collision
paths for getWidgetIdentity: add a same-local-id/different-scope case (e.g., two
widgets created via createMockWidget with nodeId/storeNodeId like "65:19" and
"66:19" but identical slot/storeName) to ensure different scopes don’t collide,
and add a hydration/transient case using the placeholder "-1" (create one widget
with nodeId/storeNodeId "-1" and another with the eventual resolved id like
"65:19") to verify deduplication handles transient-to-resolved identity changes;
keep using createMockNodeData and mountComponent and assert the expected widget
count as in the existing test so getWidgetIdentity’s collision bug is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/core/graph/subgraph/promotedWidgetView.test.ts`:
- Around line 1562-1568: The test currently allows zero resolver calls which can
hide regressions; change the assertion around resolveSpy and
subgraphNode.widgets so the first access actually triggered resolution and the
second access is idempotent: after the first read of subgraphNode.widgets
capture resolveSpy.mock.calls.length and assert it is at least 1 (e.g.
expect(initialResolveCount).toBeGreaterThanOrEqual(1) or equal 1) to ensure
resolution ran, then call void subgraphNode.widgets again and assert resolveSpy
was not incremented
(expect(resolveSpy).toHaveBeenCalledTimes(initialResolveCount)); use the
existing resolveSpy and subgraphNode.widgets identifiers when updating the test.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts`:
- Around line 139-173: Update the test in NodeWidgets.test.ts to cover the
missing collision paths for getWidgetIdentity: add a
same-local-id/different-scope case (e.g., two widgets created via
createMockWidget with nodeId/storeNodeId like "65:19" and "66:19" but identical
slot/storeName) to ensure different scopes don’t collide, and add a
hydration/transient case using the placeholder "-1" (create one widget with
nodeId/storeNodeId "-1" and another with the eventual resolved id like "65:19")
to verify deduplication handles transient-to-resolved identity changes; keep
using createMockNodeData and mountComponent and assert the expected widget count
as in the existing test so getWidgetIdentity’s collision bug is exercised.

In `@src/renderer/extensions/vueNodes/components/NodeWidgets.vue`:
- Around line 250-264: stableIdentityRoot currently prefers rawWidgetId and runs
stripGraphPrefix on it which collapses scoped ids and treats placeholder -1 as
stable; change the logic in the stableIdentityRoot/dedupeIdentity/renderKey
computation to prefer widget.sourceExecutionId when present, otherwise use the
full scoped rawWidgetId (do not call stripGraphPrefix), and treat rawWidgetId
values that are unset or equal to "-1" as unresolved so the renderKey falls back
to the transient form; ensure dedupeIdentity still composes from
stableIdentityRoot, storeWidgetName, slotNameForIdentity, and widget.type when
stableIdentityRoot is defined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 93cf6d98-2c28-452e-b52c-43e04a3be414

📥 Commits

Reviewing files that changed from the base of the PR and between 6ab59e9 and 52cc623.

📒 Files selected for processing (5)
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.test.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue

@DrJKL DrJKL added preview needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch core/1.41 Backport PRs for core 1.41 labels Mar 14, 2026
@DrJKL DrJKL added the cloud/1.41 Backport PRs for cloud 1.41 label Mar 14, 2026
}

set value(value: IBaseWidget['value']) {
const linkedWidgets = this.getLinkedInputWidgets()
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: get value() reads from getLinkedInputWidgetStates()[0] (the first linked state), while set value() writes to ALL linked widgets plus the deepest-resolved widget. If getLinkedInputWidgets() returns entries in non-deterministic order, getter and setter operate on different state objects.

Also, if some getWidget() calls return undefined during the setter loop, partial updates occur -- some linked states get the new value, others don't, and the didUpdateState flag prevents the fallback path from running.

Consider making the getter read from the same canonical state the setter writes to as its primary target.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re-checked this deeply and kept behavior unchanged for now (。•ㅅ•。). The read and write asymmetry is intentional in this flow: representative read path plus fanout write path for linked consistency.

Avoided semantic expansion in this PR and focused on correctness-critical fixes first ♡.

)
}

private getLinkedInputWidgets(): Array<{
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: getLinkedInputWidgets() performs a linear scan of all inputs with nested calls to matchPromotedInput(), getConnectedWidgets(), and filter(hasWidgetNode). It's called from getWidgetState() -> value getter on every render frame, but unlike resolveDeepest() (which has cachedDeepestByFrame), it has no caching. Consider adding frame-based caching similar to cachedDeepestByFrame.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very fair perf thought~ (´꒳`)♡ We did not add caching in this pass to keep invalidation complexity low without profiler evidence of a hotspot.

If perf traces show this path is hot, we can add a minimal frame-scoped cache follow-up (˶ᵔ ᵕ ᵔ˶)/.

return deduplicatedEntries
}

private _hasMissingBoundSourceWidget(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: _hasMissingBoundSourceWidget() iterates all inputs with getNodeById + widget array scan, and it's called as a cache-freshness signal on every widgets getter access (i.e., every frame) from both _getLinkedPromotionEntries() and _getPromotedViews(). This creates a hybrid cache invalidation model (reactive heuristic bypassing _cacheVersion) that's hard to reason about. Consider either making it part of the dirty-flag invalidation (set on relevant events) or removing it from the cache key and relying solely on _cacheVersion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reviewed this skeptically and decided not to redesign freshness invalidation in this PR ૮₍˶ •. • ⑅₎ა.

Keeping the current model for now to avoid broad architectural risk while landing concrete correctness fixes first.


const existingWidget = uniqueWidgets[existingIndex]
if (existingWidget && !existingWidget.isVisible && visible) {
uniqueWidgets[existingIndex] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: When a duplicate is found and the replacement is visible but the original wasn't, the entire entry is replaced including identity -- which may have a different renderKey. Since renderKey is used as Vue's :key, this forces Vue to destroy and recreate the component rather than patch in-place. Consider preserving the original renderKey when replacing the dedup entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Double-checked this path and did not apply a change ( •̀ᴗ•́ )✧.

In the dedupe replacement branch, dedupeIdentity and renderKey remain aligned, so that specific replacement flow should not force remount by key change.

inputName = ''
): string {
return `${inputName}:${interiorNodeId}:${widgetName}`
return `${inputKey}:${interiorNodeId}:${widgetName}:${inputName}`
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: The key format ${inputKey}:${interiorNodeId}:${widgetName}:${inputName} uses : as delimiter, but node IDs can contain colons (e.g., "65:18" for subgraph execution paths), creating ambiguous key boundaries. Consider using a delimiter that cannot appear in any component (e.g., \x1F unit separator) or JSON.stringify([...]).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented this one~ ✨ We switched SubgraphNode._makePromotionViewKey to structured encoding so key generation is unambiguous and collision-safe even when segments contain separator-like text (。•̀ᴗ-)✧.

Added regression coverage for collision-shaped tuples too, so this path is now locked down nicely ದ್ದി(˵ •̀ ᴗ - ˵ ).

} as IBaseWidget
}

describe(getStableWidgetRenderKey, () => {
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 test file covers key stability and uniqueness but doesn't test the promoted vs widget prefix branch -- the function's primary differentiator (isPromotedWidgetView(widget) ? 'promoted' : 'widget'). Consider adding a test with a mock PromotedWidgetView verifying the key starts with 'promoted:'.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Totally reasonable suggestion~ ♡(˶╹̆ ▿ ╹̆˵)♡

Treated this as non-blocking in this pass and prioritized higher-impact correctness items first. Happy to add this branch-coverage polish in a tiny follow-up (^_^)/.

if (!inputs) return undefined

return (
inputs.find((input) => input._widget === widget) ??
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 name-fallback path (inputs.find((input) => input.name === widget.name)) can match the wrong input when there are duplicate names -- the exact scenario this PR is fixing. The first name match wins, which may be incorrect. Consider documenting the constraint that callers must scope inputs to avoid ambiguity, or removing the name fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed with a stricter fallback in matchPromotedInput (´。• ᵕ •。`)/. We still prioritize exact _widget matching, and now name fallback only applies when the name is uniquely matched; duplicate-name ambiguity now returns undefined instead of guessing ૮꒰ ˶• ༝ •˶꒱ა.

Added tests for both unique fallback and duplicate rejection.

const cachedViews = this._promotedViewsCache
if (
cachedViews?.version === this._cacheVersion &&
cachedViews.entriesRef === entries &&
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): cachedViews.entriesRef === entries uses reference equality, but getPromotionsRef() returns ?? [] (in promotionStore.ts:65), which creates a new array on every call for nodes with no promotions. This means the cache always misses for those nodes. Consider caching a stable empty-array sentinel in getPromotionsRef.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented~ We now return a stable shared empty ref from getPromotionsRef in the empty case, so referential checks stay stable and we avoid unnecessary cache churn (˶ᵔ ᵕ ᵔ˶).

Added a dedicated test for stable empty ref behavior too (ฅ́˘ฅ̀).

if (!subgraphData)
throw new Error('Expected fixture to contain one subgraph definition')

LiteGraph.registerNodeType(
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): FixtureStringConcatenateNode is registered globally via LiteGraph.registerNodeType but never unregistered. This leaks across test files and could cause ordering-dependent test failures. Consider adding cleanup in afterEach or using vitest hooks to unregister after tests complete.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great catch, fixed! We added fixture node-type cleanup so Fixture/StringConcatenate registration is torn down cleanly and does not leak across tests ᐢ•˕•ᐢ.

Also added a focused helper cleanup test and wired teardown in the fixture-heavy promoted-widget tests (❀◠‿◠).

@christian-byrne christian-byrne removed their assignment Mar 14, 2026
- avoid ambiguous same-name fallback in matchPromotedInput

- use unambiguous promotion view key encoding in SubgraphNode

- stabilize empty promotion refs for cache checks

- add fixture node-type teardown and regression tests

Amp-Thread-ID: https://ampcode.com/threads/T-019ceacb-1ff8-723b-bd39-29e41cb522b0
Co-authored-by: Amp <amp@ampcode.com>
@DrJKL DrJKL requested a review from christian-byrne March 14, 2026 06:30
@DrJKL DrJKL assigned christian-byrne and unassigned DrJKL Mar 14, 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

543-557: ⚠️ Potential issue | 🟠 Major

Use collision-free encoding for the remaining promotion and slot signatures.

_makePromotionEntryKey() and the rebind signature still concatenate arbitrary strings with :. The new colon-placement regression test shows names like a:b are in scope, and scoped node ids can also contain :. Different tuples can therefore collapse to the same key, which can prune the wrong fallback promotion or rebind the wrong subgraph slot during configure/hydration.

🔧 Suggested fix
-      const signature = `${slot.name}:${String(slot.type)}`
+      const signature = JSON.stringify([slot.name, String(slot.type)])
@@
-      const signature = `${input.name}:${String(input.type)}`
+      const signature = JSON.stringify([input.name, String(input.type)])
@@
-    return `${interiorNodeId}:${widgetName}`
+    return JSON.stringify([interiorNodeId, widgetName])

Also applies to: 823-864

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

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts` around lines 543 - 557, The
current key construction in _makePromotionEntryKey and the rebind/signature
logic concatenates arbitrary strings with ":" which can collide for values
containing colons; change _makePromotionEntryKey to produce a collision-free
encoding (e.g. JSON.stringify([interiorNodeId, widgetName])) and update any
related rebind/signature and slot-promotion key generators to use the same
tuple-encoding approach (use JSON.stringify on arrays of the components) so all
promotion, view and slot keys are produced consistently and avoid collisions.
🧹 Nitpick comments (1)
src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts (1)

12-12: Export FIXTURE_STRING_CONCAT_TYPE from subgraphHelpers.ts and import it in the test to avoid duplication.

The constant FIXTURE_STRING_CONCAT_TYPE is defined in both subgraphHelpers.ts and this test file. It's currently not exported from subgraphHelpers.ts, but exporting it and importing it here would establish a single source of truth, reducing the risk of accidental drift if the fixture type name changes.

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

In `@src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts` at line
12, The constant FIXTURE_STRING_CONCAT_TYPE is duplicated in the test; export
FIXTURE_STRING_CONCAT_TYPE from subgraphHelpers.ts and remove the local
declaration in subgraphHelpers.test.ts, then import { FIXTURE_STRING_CONCAT_TYPE
} from the subgraphHelpers module in the test; ensure the export is a named
export (export const FIXTURE_STRING_CONCAT_TYPE = 'Fixture/StringConcatenate')
and update any references in the test to use the imported symbol.
🤖 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/core/graph/subgraph/promotedWidgetView.test.ts`:
- Around line 107-109: Hoist the fixture cleanup to the file scope so the
registered LiteGraph node type is removed for all tests: remove the inner
describe's afterEach(() => cleanupComplexPromotionFixtureNodeType()) and add a
top-level afterEach(() => cleanupComplexPromotionFixtureNodeType()) (or afterAll
if you prefer cleanup only once after the whole file) next to the file-scope
setupComplexPromotionFixture() calls so cleanupComplexPromotionFixtureNodeType()
always runs after tests that call setupComplexPromotionFixture(); apply the same
change for the other block covering SubgraphNode.widgets getter (the later range
mentioned).

---

Duplicate comments:
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 543-557: The current key construction in _makePromotionEntryKey
and the rebind/signature logic concatenates arbitrary strings with ":" which can
collide for values containing colons; change _makePromotionEntryKey to produce a
collision-free encoding (e.g. JSON.stringify([interiorNodeId, widgetName])) and
update any related rebind/signature and slot-promotion key generators to use the
same tuple-encoding approach (use JSON.stringify on arrays of the components) so
all promotion, view and slot keys are produced consistently and avoid
collisions.

---

Nitpick comments:
In `@src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts`:
- Line 12: The constant FIXTURE_STRING_CONCAT_TYPE is duplicated in the test;
export FIXTURE_STRING_CONCAT_TYPE from subgraphHelpers.ts and remove the local
declaration in subgraphHelpers.test.ts, then import { FIXTURE_STRING_CONCAT_TYPE
} from the subgraphHelpers module in the test; ensure the export is a named
export (export const FIXTURE_STRING_CONCAT_TYPE = 'Fixture/StringConcatenate')
and update any references in the test to use the imported symbol.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4645c850-02fc-4214-9e9e-f76ab66a73dc

📥 Commits

Reviewing files that changed from the base of the PR and between 52cc623 and 2ce5e4e.

📒 Files selected for processing (9)
  • src/core/graph/subgraph/matchPromotedInput.test.ts
  • src/core/graph/subgraph/matchPromotedInput.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.test.ts
  • src/lib/litegraph/src/subgraph/__fixtures__/subgraphHelpers.ts
  • src/stores/promotionStore.test.ts
  • src/stores/promotionStore.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/litegraph/src/subgraph/fixtures/subgraphHelpers.ts
  • src/core/graph/subgraph/matchPromotedInput.test.ts
  • src/core/graph/subgraph/matchPromotedInput.ts

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

Labels

area:litegraph area:subgraph area:vue-nodes Vue Nodes 2.0 implementation area:widgets cloud/1.41 Backport PRs for cloud 1.41 core/1.41 Backport PRs for core 1.41 needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch preview size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants