Skip to content

fix: stabilize nested subgraph promoted widget resolution#9282

Merged
DrJKL merged 26 commits intomainfrom
drjkl/preview-fix
Feb 28, 2026
Merged

fix: stabilize nested subgraph promoted widget resolution#9282
DrJKL merged 26 commits intomainfrom
drjkl/preview-fix

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Feb 27, 2026

Summary

Fix multiple issues with promoted widget resolution in nested subgraphs, ensuring correct value propagation, slot matching, and rendering for deeply nested promoted widgets.

Changes

  • What: Stabilize nested subgraph promoted widget resolution chain
    • Use deep source keys for promoted widget values in Vue rendering mode
    • Resolve effective widget options from the source widget instead of the promoted view
    • Stabilize slot resolution for nested promoted widgets
    • Preserve combo value rendering for promoted subgraph widgets
    • Prevent subgraph definition deletion while other nodes still reference the same type
    • Clean up unused exported resolution types

Review Focus

  • resolveConcretePromotedWidget.ts — new recursive resolution logic for deeply nested promoted widgets
  • useGraphNodeManager.ts — option extraction now uses effectiveWidget for promoted widgets
  • SubgraphNode.ts — unpack no longer force-deletes definitions referenced by other nodes

┆Issue is synchronized with this Notion page by Unito

DrJKL and others added 6 commits February 27, 2026 00:30
- align projected widget disabled state with promoted view state during draw

- add regression coverage for combo promotion with disabled interior widget

Amp-Thread-ID: https://ampcode.com/threads/T-019c9c34-1142-726d-9fb2-b5b6dcb4c435
Co-authored-by: Amp <amp@ampcode.com>
- prevent subgraph definition deletion while other nodes still reference the same subgraph type

- stop unpack from force-deleting definitions after removing the unpacked node

- add regression coverage for unpacking one instance while another remains

Amp-Thread-ID: https://ampcode.com/threads/T-019c9e10-f581-7091-b103-05ec41896bda
Co-authored-by: Amp <amp@ampcode.com>
- make promoted widget resolution helper types internal to satisfy knip pre-push

Amp-Thread-ID: https://ampcode.com/threads/T-019c9e10-f581-7091-b103-05ec41896bda
Co-authored-by: Amp <amp@ampcode.com>
@github-actions
Copy link

github-actions bot commented Feb 27, 2026

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

📊 Browser Reports
  • chromium: View Report (✅ 534 / ❌ 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 Feb 27, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/28/2026, 09:38:31 PM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 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
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Implements multi-stage promoted-widget resolution across nested subgraphs, surfaces concrete source metadata into widget mappings (storeNodeId/storeName), adds buffered/linked promotion handling with stable view keys and caching, updates renderer/state lookups to use store-backed identifiers, and adds extensive tests and assets for nested promotion behaviors.

Changes

Cohort / File(s) Summary
Promoted resolution core
src/core/graph/subgraph/resolveConcretePromotedWidget.ts, src/core/graph/subgraph/resolvePromotedWidgetSource.ts
Add multi-stage resolver with traversal, cycle detection, failure kinds, lookup-target helper; resolvePromotedWidgetSource delegates to concrete resolver and returns resolved concrete widget.
Promoted types & input-link helper
src/core/graph/subgraph/promotedWidgetTypes.ts, src/core/graph/subgraph/resolveSubgraphInputLink.ts
Add ResolvedPromotedWidget type and resolveSubgraphInputLink utility to trace subgraph input links to interior widgets.
Promoted view & state lookup
src/core/graph/subgraph/promotedWidgetView.ts, src/core/graph/subgraph/promotedWidgetView.test.ts
PromotedWidgetView now resolves concrete widgets for getters, adds state-lookup targeting, tracks projectedWidgetType and computedDisabled lifecycle, and includes extensive tests for rendering/behavior.
Graph node manager / widget mapping
src/composables/graph/useGraphNodeManager.ts
SafeWidgetData now exports storeNodeId?/storeName?; mapping computes promotedSourceSeed/displayName, resolves effectiveWidget (promoted or original), populates slotMetadata and store fields for promoted subgraphs.
Promoted view caching & keys
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
Introduce optional viewKey on PromotionEntry; key generation, cache lookup, reconcile, getOrCreate extended; add removeByViewKey to invalidate view by viewKey.
Subgraph promotion lifecycle
src/lib/litegraph/src/subgraph/SubgraphNode.ts, src/lib/litegraph/src/subgraph/SubgraphInput.ts
Buffered pending promotions flushed on add, linked-promotion resolution by input name, merge/reconcile state with view keys/display names, promotion demote/remove flows, clear stale input widget refs on disconnect, and serialize sync.
Litegraph graph lifecycle & tests
src/lib/litegraph/src/LGraph.ts, src/lib/litegraph/src/LGraph.test.ts
Defer deletion of subgraph definitions until no references remain; preserve subgraph defs when unpacking one of multiple instances; add test for preservation.
Renderer integration
src/renderer/extensions/vueNodes/components/NodeWidgets.vue, src/components/graph/widgets/DomWidget.vue
Use widget.storeNodeId ?? widget.nodeId and widget.storeName ?? widget.name for state lookups; centralize promoted-view flag; DOM widget respects computedDisabled (including overrides) for pointerEvents/opacity.
Resolve & integration tests + assets
src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts, src/core/graph/subgraph/resolveConcretePromotedWidget.test.ts, browser_tests/assets/subgraphs/subgraph-nested-promotion.json, browser_tests/tests/subgraphPromotion.spec.ts
Add comprehensive unit/browser tests and nested-promotion JSON asset covering concrete resolution, failure modes, nested promotions, rendering, disabled-state propagation, and interactive behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Consumer
    participant GM as useGraphNodeManager
    participant SN as SubgraphNode
    participant RCP as resolveConcretePromotedWidget
    participant PV as PromotedWidgetView
    participant Store as WidgetStore

    Consumer->>GM: request mapped widget info
    GM->>SN: compute promotedSourceSeed / resolveSubgraphInputLink
    GM->>RCP: resolveConcretePromotedWidget(hostNode, nodeId, widgetName)
    RCP->>RCP: traverse chain (detect cycle, cap depth)
    alt resolved
        RCP-->>GM: ResolvedPromotedWidget { node, widget }
    else failure
        RCP-->>GM: failure reason
    end
    GM->>PV: derive effectiveWidget and store metadata (storeNodeId/storeName)
    PV->>Store: lookup state using storeNodeId/storeName
    Store-->>PV: widget state
    PV-->>GM: SafeWidgetData (including storeNodeId/storeName, displayName, computedDisabled)
    GM-->>Consumer: mapped widget payload
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hop through nested view and name,

I trace the seed and chase the frame,
From host to core I follow tight,
Promoted paths made clear and bright,
Concrete at last — a joyful sight. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: stabilizing nested subgraph promoted widget resolution, which is the core objective of this PR with substantial changes across multiple files.
Description check ✅ Passed The description covers the required sections (Summary, Changes, Review Focus) and provides specific details about what was changed and why, though it could be slightly more comprehensive in some areas.

✏️ 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/preview-fix

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts (1)

98-104: Namespace viewKey in cache keys to prevent accidental collisions.

At Line 103, using the raw viewKey can cause unrelated widgets to reuse the same cached view if they share that value.

Suggested adjustment
   private makeKey(
     interiorNodeId: string,
     widgetName: string,
     viewKey?: string
   ): string {
-    return viewKey ?? `${interiorNodeId}:${widgetName}`
+    return viewKey
+      ? `${interiorNodeId}:${widgetName}:${viewKey}`
+      : `${interiorNodeId}:${widgetName}`
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts` around lines 98
- 104, The cache key builder makeKey(interiorNodeId, widgetName, viewKey)
currently returns the raw viewKey which can collide across widgets; change it to
namespace the viewKey (e.g., prefix with a fixed token like "vk:" or include
widgetName) before using it in the key so cached entries are unique per widget
and node. Update makeKey to compute a namespacedViewKey from viewKey when
present and then return `${interiorNodeId}:${widgetName}:${namespacedViewKey}`
(or omit the extra segment when viewKey is absent) so other functions using
makeKey get distinct keys.
src/core/graph/subgraph/promotedWidgetView.test.ts (1)

995-1368: Extract a shared two-layer subgraph fixture to reduce test drift.

The same nested graph construction is repeated across multiple tests; pulling it into one helper will make future resolver changes safer and reduce copy/paste maintenance.

♻️ Suggested refactor sketch
+function createTwoLayerPromotionFixture() {
+  const subgraphA = createTestSubgraph({
+    inputs: [{ name: 'a_input', type: '*' }]
+  })
+  const innerNode = new LGraphNode('InnerNode')
+  const innerInput = innerNode.addInput('picker_input', '*')
+  const widget = innerNode.addWidget('combo', 'picker', 'a', () => {}, {
+    values: ['a', 'b']
+  })
+  innerInput.widget = { name: 'picker' }
+  subgraphA.add(innerNode)
+  subgraphA.inputNode.slots[0].connect(innerInput, innerNode)
+
+  const subgraphNodeA = createTestSubgraphNode(subgraphA, { id: 11 })
+  const subgraphB = createTestSubgraph({
+    inputs: [{ name: 'b_input', type: '*' }]
+  })
+  subgraphB.add(subgraphNodeA)
+  subgraphNodeA._internalConfigureAfterSlots()
+  subgraphB.inputNode.slots[0].connect(subgraphNodeA.inputs[0], subgraphNodeA)
+  const subgraphNodeB = createTestSubgraphNode(subgraphB, { id: 22 })
+
+  return { subgraphNodeA, subgraphNodeB, innerNode, widget }
+}
🤖 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 995 - 1368,
Many tests repeat the same two-layer nested subgraph construction (using
createTestSubgraph, createTestSubgraphNode, LGraphNode, innerNode,
subgraphNodeA, subgraphNodeB, input wiring and widget setup); extract that setup
into a single helper (e.g., createTwoLayerPromotedSubgraph) that returns the
relevant objects (subgraphA, innerNode, comboWidget/inner widgets,
subgraphNodeA, subgraphB, subgraphNodeB) and update each test to call this
helper and operate on the returned handles instead of duplicating the
construction logic so tests are shorter and less error-prone when the resolver
changes.
🤖 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/composables/graph/useGraphNodeManager.ts`:
- Line 348: The slotName assignment uses an undefined identifier `name` which
breaks TypeScript; update the ternary to compare against the in-scope
`displayName` (i.e., set slotName: displayName !== widget.name ? widget.name :
undefined) so the code compiles; locate this in the useGraphNodeManager
composable where `slotName` is set alongside `widget` and `displayName`.

In `@src/core/graph/subgraph/resolveConcretePromotedWidget.ts`:
- Around line 82-92: The cycle detection uses a string key built from
currentHost.id which can collide across different host objects; in
resolveConcretePromotedWidget replace use of currentHost.id in visitKey with a
host-object-unique identifier by creating a WeakMap<Host, number> (e.g.,
hostUidMap) and assign an incremental uid per host, then build visitKey =
`${hostUidMap.get(currentHost)}:${currentNodeId}:${currentWidgetName}`; apply
the same change to the other occurrence that builds visitKey (the second
cycle-check block using visited, visitKey,
currentHost/currentNodeId/currentWidgetName) so cycle tracking is scoped to host
object identity rather than numeric id.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 223-225: The promotion write can occur while the node id is still
-1, so guard the store reconciliation to avoid persisting promotions for a
placeholder id: before calling store.setPromotions(this.rootGraph.id, this.id,
finalEntries) (inside the hasCompleteLinkedCoverage && hasChanged branch),
ensure this.id !== -1 (and/or that the node has been added) and that widgets
haven’t been accessed prematurely; only call store.setPromotions when the node
has a valid id to respect the deferred-promotion contract.

---

Nitpick comments:
In `@src/core/graph/subgraph/promotedWidgetView.test.ts`:
- Around line 995-1368: Many tests repeat the same two-layer nested subgraph
construction (using createTestSubgraph, createTestSubgraphNode, LGraphNode,
innerNode, subgraphNodeA, subgraphNodeB, input wiring and widget setup); extract
that setup into a single helper (e.g., createTwoLayerPromotedSubgraph) that
returns the relevant objects (subgraphA, innerNode, comboWidget/inner widgets,
subgraphNodeA, subgraphB, subgraphNodeB) and update each test to call this
helper and operate on the returned handles instead of duplicating the
construction logic so tests are shorter and less error-prone when the resolver
changes.

In `@src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts`:
- Around line 98-104: The cache key builder makeKey(interiorNodeId, widgetName,
viewKey) currently returns the raw viewKey which can collide across widgets;
change it to namespace the viewKey (e.g., prefix with a fixed token like "vk:"
or include widgetName) before using it in the key so cached entries are unique
per widget and node. Update makeKey to compute a namespacedViewKey from viewKey
when present and then return
`${interiorNodeId}:${widgetName}:${namespacedViewKey}` (or omit the extra
segment when viewKey is absent) so other functions using makeKey get distinct
keys.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7901e14 and 4c0db3e.

📒 Files selected for processing (12)
  • src/composables/graph/useGraphNodeManager.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.ts
  • src/core/graph/subgraph/resolvePromotedWidgetSource.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/renderer/extensions/vueNodes/components/NodeWidgets.vue
  • src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts

@github-actions
Copy link

github-actions bot commented Feb 27, 2026

📦 Bundle: 4.47 MB gzip 🔴 +2.74 kB

Details

Summary

  • Raw size: 21 MB baseline 20.9 MB — 🔴 +13.4 kB
  • Gzip: 4.47 MB baseline 4.47 MB — 🔴 +2.74 kB
  • Brotli: 3.45 MB baseline 3.45 MB — 🔴 +2.13 kB
  • Bundles: 228 current • 228 baseline • 108 added / 108 removed

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

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

Main entry bundles and manifests

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

Status: 1 added / 1 removed

Graph Workspace — 1.03 MB (baseline 1.03 MB) • 🔴 +2.16 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BbKFbTbz.js (new) 1.03 MB 🔴 +1.03 MB 🔴 +220 kB 🔴 +166 kB
assets/GraphView-CxVpDsoo.js (removed) 1.03 MB 🟢 -1.03 MB 🟢 -219 kB 🟢 -166 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-CRUuIyXd.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.82 kB
assets/CloudSurveyView-D5v9MA_P.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.82 kB
assets/CloudLoginView-CQKHQC3k.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.19 kB 🟢 -2.82 kB
assets/CloudLoginView-CSrlp1m5.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.19 kB 🔴 +2.81 kB
assets/CloudSignupView-DmUPYF6n.js (new) 9.37 kB 🔴 +9.37 kB 🔴 +2.69 kB 🔴 +2.36 kB
assets/CloudSignupView-DszpF8at.js (removed) 9.37 kB 🟢 -9.37 kB 🟢 -2.7 kB 🟢 -2.37 kB
assets/UserCheckView-BnH_VK-g.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-DroiDMh_.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudLayoutView-BAVhWjT1.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.83 kB
assets/CloudLayoutView-DRDED_T-.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.82 kB
assets/CloudForgotPasswordView-BeYqr7h-.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.7 kB
assets/CloudForgotPasswordView-Bio09LKI.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.93 kB 🟢 -1.71 kB
assets/CloudAuthTimeoutView-D7KzFjMn.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.55 kB
assets/CloudAuthTimeoutView-WTPzykkD.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.54 kB
assets/CloudSubscriptionRedirectView-BB1pnfap.js (new) 4.75 kB 🔴 +4.75 kB 🔴 +1.78 kB 🔴 +1.58 kB
assets/CloudSubscriptionRedirectView-BkMHKWEX.js (removed) 4.75 kB 🟢 -4.75 kB 🟢 -1.79 kB 🟢 -1.58 kB
assets/UserSelectView-BkGNZg9r.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.46 kB
assets/UserSelectView-CrLu4IMd.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.46 kB
assets/CloudSorryContactSupportView-Bypca0av.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-C8IrwMzw.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Panels & Settings — 435 kB (baseline 435 kB) • 🔴 +1 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-BaGIa-zZ.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.31 kB 🔴 +4.65 kB
assets/SecretsPanel-DnuMCdiF.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.31 kB 🟢 -4.66 kB
assets/LegacyCreditsPanel-C0y_bM8G.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.56 kB 🔴 +4.89 kB
assets/LegacyCreditsPanel-DpFj5gzA.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.56 kB 🟢 -4.9 kB
assets/SubscriptionPanel-BzUqjc4u.js (new) 18.2 kB 🔴 +18.2 kB 🔴 +4.66 kB 🔴 +4.09 kB
assets/SubscriptionPanel-CFRlkceQ.js (removed) 18.2 kB 🟢 -18.2 kB 🟢 -4.65 kB 🟢 -4.1 kB
assets/KeybindingPanel-D7iJxtfJ.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.52 kB 🟢 -3.12 kB
assets/KeybindingPanel-DaHa2F6k.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.52 kB 🔴 +3.12 kB
assets/AboutPanel-7ObvyABO.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.46 kB
assets/AboutPanel-L8zgHkpq.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.45 kB
assets/ExtensionPanel-Buq5EMjo.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/ExtensionPanel-DPO_6Aj4.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.35 kB
assets/ServerConfigPanel-7WEGyqAw.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.13 kB 🔴 +1.93 kB
assets/ServerConfigPanel-B1vu8hKZ.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.13 kB 🟢 -1.92 kB
assets/UserPanel-CPJgDZpx.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/UserPanel-RAnoLLeJ.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +1.99 kB 🔴 +1.74 kB
assets/cloudRemoteConfig-DqMl9ZX6.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -706 B 🟢 -606 B
assets/cloudRemoteConfig-ValsRmk_.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +705 B 🔴 +612 B
assets/refreshRemoteConfig-BiQOMRH3.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +519 B 🔴 +470 B
assets/refreshRemoteConfig-DVCmcHNF.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -520 B 🟢 -472 B
assets/config-CGn5JFmU.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-B5oF6TeI.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BVYOg4dh.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CBEvSL1z.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CGx1t8IZ.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CNcb_4nC.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Cx1dZM6H.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Dw-QS6Nb.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DXxgnCSn.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-GRFn4guL.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-mgwKIVQ2.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-s83B801I.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-Cqyd8IkT.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -993 B
assets/auth-DrCZg2aC.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +990 B
assets/SignUpForm-DOeK8iUz.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/SignUpForm-t8QkzspV.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/UpdatePasswordContent-BLI5aynF.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.07 kB 🔴 +942 B
assets/UpdatePasswordContent-Cj_K_Jmz.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -938 B
assets/firebaseAuthStore-DhQlB0Jc.js (removed) 788 B 🟢 -788 B 🟢 -389 B 🟢 -375 B
assets/firebaseAuthStore-DwvZxYSg.js (new) 788 B 🔴 +788 B 🔴 +387 B 🔴 +345 B
assets/auth-B8y7IRri.js (removed) 357 B 🟢 -357 B 🟢 -226 B 🟢 -210 B
assets/auth-C8TTm49s.js (new) 357 B 🔴 +357 B 🔴 +224 B 🔴 +193 B
assets/PasswordFields-DLbVLg8O.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-D6ioir1T.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

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

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-BnjSjTN0.js (new) 736 B 🔴 +736 B 🔴 +376 B 🔴 +325 B
assets/useSubscriptionDialog-ebnFIGLr.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -356 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-BxKr5tOp.js (removed) 9.84 kB 🟢 -9.84 kB 🟢 -3.39 kB 🟢 -3 kB
assets/useTerminalTabs-C5YudovD.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.39 kB 🔴 +3 kB
assets/ComfyQueueButton-CwsmMy38.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.23 kB
assets/ComfyQueueButton-DAis90Fn.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.23 kB
assets/SubscribeButton-_pGIXL1h.js (new) 2.48 kB 🔴 +2.48 kB 🔴 +1.07 kB 🔴 +942 B
assets/SubscribeButton-CeFpVtTk.js (removed) 2.48 kB 🟢 -2.48 kB 🟢 -1.07 kB 🟢 -947 B
assets/cloudFeedbackTopbarButton-Crk0vxZH.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +852 B 🔴 +757 B
assets/cloudFeedbackTopbarButton-DP7OEVKB.js (removed) 1.59 kB 🟢 -1.59 kB 🟢 -852 B 🟢 -761 B
assets/ComfyQueueButton-BRu2rxdI.js (removed) 793 B 🟢 -793 B 🟢 -392 B 🟢 -357 B
assets/ComfyQueueButton-DxWkh2-C.js (new) 793 B 🔴 +793 B 🔴 +393 B 🔴 +348 B
assets/Button-BRQwfydz.js 2.98 kB 2.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-Dwm2ik5F.js 1.16 kB 1.16 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-_-8jCXXp.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-DBNsZCG7.js 5.94 kB 5.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge-CcfLKchp.js 7.44 kB 7.44 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-CnQQLXB-.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-BA0BMQcz.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.57 MB (baseline 2.56 MB) • 🔴 +11.3 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-BDE938td.js (removed) 1.76 MB 🟢 -1.76 MB 🟢 -395 kB 🟢 -297 kB
assets/dialogService-CFdI0VeD.js (new) 1.76 MB 🔴 +1.76 MB 🔴 +395 kB 🔴 +297 kB
assets/api-DnapY_5P.js (new) 688 kB 🔴 +688 kB 🔴 +155 kB 🔴 +123 kB
assets/api-C1IAYWGl.js (removed) 677 kB 🟢 -677 kB 🟢 -153 kB 🟢 -122 kB
assets/load3dService-C7tFCAy8.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/load3dService-Cs3KFXKQ.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/extensionStore-BcqiEo7S.js (removed) 12.1 kB 🟢 -12.1 kB 🟢 -4.21 kB 🟢 -3.7 kB
assets/extensionStore-DOArvsBN.js (new) 12.1 kB 🔴 +12.1 kB 🔴 +4.21 kB 🔴 +3.7 kB
assets/releaseStore-BJh0NduP.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/releaseStore-0EFVOX9v.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/keybindingService-_UynQbHJ.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/keybindingService-CG_K60uJ.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.48 kB
assets/bootstrapStore-7Ma7dHzX.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +874 B 🔴 +786 B
assets/bootstrapStore-DDjF2gVi.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -874 B 🟢 -789 B
assets/userStore-DKwTPc4Z.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +721 B 🔴 +674 B
assets/userStore-G5CRu07R.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -722 B 🟢 -671 B
assets/audioService-Bmr5keAm.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +846 B 🔴 +722 B
assets/audioService-DMLV2nSv.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -847 B 🟢 -725 B
assets/releaseStore-BxV5z7Ij.js (new) 760 B 🔴 +760 B 🔴 +381 B 🔴 +337 B
assets/releaseStore-CADy1KtA.js (removed) 760 B 🟢 -760 B 🟢 -385 B 🟢 -341 B
assets/settingStore-19ob1qjM.js (removed) 744 B 🟢 -744 B 🟢 -385 B 🟢 -338 B
assets/settingStore-CSngpjgm.js (new) 744 B 🔴 +744 B 🔴 +384 B 🔴 +340 B
assets/workflowDraftStore-BDbfeo-j.js (removed) 736 B 🟢 -736 B 🟢 -378 B 🟢 -335 B
assets/workflowDraftStore-C-C_q609.js (new) 736 B 🔴 +736 B 🔴 +376 B 🔴 +333 B
assets/dialogService-BMjvQ5Kr.js (new) 725 B 🔴 +725 B 🔴 +367 B 🔴 +326 B
assets/dialogService-Ck2OZDxk.js (removed) 725 B 🟢 -725 B 🟢 -367 B 🟢 -326 B
assets/serverConfigStore-EPk4OtIK.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-CUegVVgj.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.2 kB
assets/useLoad3d-DXSSD6dq.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3dViewer-Db7W0bDd.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.79 kB
assets/useLoad3dViewer-EbVpWbeu.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.79 kB
assets/useFeatureFlags-Bmbk0oaB.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.06 kB
assets/useFeatureFlags-Dv2aL4jC.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/useWorkspaceUI-CWVy_gHL.js (new) 3 kB 🔴 +3 kB 🔴 +821 B 🔴 +707 B
assets/useWorkspaceUI-DLwVRl7Q.js (removed) 3 kB 🟢 -3 kB 🟢 -822 B 🟢 -704 B
assets/subscriptionCheckoutUtil-BsvV3Dw9.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +951 B
assets/subscriptionCheckoutUtil-BWvHiQu7.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -929 B
assets/useErrorHandling-Bz3UNPcy.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +629 B 🔴 +534 B
assets/useErrorHandling-Nyg8NdJ2.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -630 B 🟢 -535 B
assets/useWorkspaceSwitch-C3kI3nfu.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -542 B 🟢 -483 B
assets/useWorkspaceSwitch-CugnqoT5.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +543 B 🔴 +484 B
assets/useLoad3d--r_D7IYN.js (removed) 859 B 🟢 -859 B 🟢 -425 B 🟢 -380 B
assets/useLoad3d-B7Jo1W-f.js (new) 859 B 🔴 +859 B 🔴 +424 B 🔴 +375 B
assets/audioUtils-BBHTPTft.js (removed) 858 B 🟢 -858 B 🟢 -500 B 🟢 -402 B
assets/audioUtils-CXcLn9Jw.js (new) 858 B 🔴 +858 B 🔴 +500 B 🔴 +402 B
assets/useLoad3dViewer-BAXnDnXQ.js (removed) 838 B 🟢 -838 B 🟢 -410 B 🟢 -371 B
assets/useLoad3dViewer-BV2iycmq.js (new) 838 B 🔴 +838 B 🔴 +409 B 🔴 +365 B
assets/useCurrentUser-DBYMxk2R.js (new) 722 B 🔴 +722 B 🔴 +371 B 🔴 +326 B
assets/useCurrentUser-MOhCNFMA.js (removed) 722 B 🟢 -722 B 🟢 -370 B 🟢 -331 B
assets/_plugin-vue_export-helper-ralzwvFM.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-Do7KfQZ0.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Clzmwvt4.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cddas8Zl.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-BputJAFn.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-BYyx-8yl.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 11 added / 11 removed

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

External libraries and shared vendor chunks

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

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-4pctZAYe.js (removed) 73.4 kB 🟢 -73.4 kB 🟢 -18.9 kB 🟢 -16.2 kB
assets/core-CNFivTOb.js (new) 73.4 kB 🔴 +73.4 kB 🔴 +18.9 kB 🔴 +16.2 kB
assets/groupNode-BNy1xQl6.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/groupNode-BVVCQtGH.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.5 kB
assets/WidgetSelect-9TfBNO5r.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/WidgetSelect-CPYPoWbj.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-DOhbYAxK.js (removed) 46.3 kB 🟢 -46.3 kB 🟢 -8.65 kB 🟢 -7.49 kB
assets/SubscriptionRequiredDialogContentWorkspace-Y30ZXKS5.js (new) 46.3 kB 🔴 +46.3 kB 🔴 +8.65 kB 🔴 +7.52 kB
assets/WidgetPainter-Bw4bU9tr.js (new) 32.5 kB 🔴 +32.5 kB 🔴 +7.96 kB 🔴 +7.06 kB
assets/WidgetPainter-CY-07Vny.js (removed) 32.5 kB 🟢 -32.5 kB 🟢 -7.96 kB 🟢 -7.05 kB
assets/Load3DControls-Bpi29Urr.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.64 kB
assets/Load3DControls-TT32lcLl.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.64 kB
assets/WorkspacePanelContent--j6biBIg.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.14 kB 🔴 +5.38 kB
assets/WorkspacePanelContent-BypCisg-.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.14 kB 🟢 -5.38 kB
assets/SubscriptionRequiredDialogContent-CY5Emp2s.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +6.57 kB 🔴 +5.78 kB
assets/SubscriptionRequiredDialogContent-DbsvitlY.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -6.56 kB 🟢 -5.78 kB
assets/Load3dViewerContent-COTb58qL.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.49 kB
assets/Load3dViewerContent-DtDN6Txk.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.49 kB
assets/WidgetImageCrop-BCn0WpDe.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.5 kB 🔴 +4.86 kB
assets/WidgetImageCrop-DNIoMPr4.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.5 kB 🟢 -4.84 kB
assets/SubscriptionPanelContentWorkspace-DqrU7Ww4.js (new) 21.6 kB 🔴 +21.6 kB 🔴 +5.05 kB 🔴 +4.46 kB
assets/SubscriptionPanelContentWorkspace-U2maz4st.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -5.05 kB 🟢 -4.45 kB
assets/CurrentUserPopoverWorkspace-BodL83yW.js (removed) 19.9 kB 🟢 -19.9 kB 🟢 -4.88 kB 🟢 -4.34 kB
assets/CurrentUserPopoverWorkspace-DX5Hf41b.js (new) 19.9 kB 🔴 +19.9 kB 🔴 +4.88 kB 🔴 +4.34 kB
assets/SignInContent-C6PKrtze.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.75 kB 🟢 -4.17 kB
assets/SignInContent-DhTX-uX6.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.75 kB 🔴 +4.16 kB
assets/WidgetInputNumber-D0xTgoix.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.75 kB 🟢 -4.22 kB
assets/WidgetInputNumber-NurPNNBz.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.22 kB
assets/WidgetRecordAudio-Bd9axmtk.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.42 kB
assets/WidgetRecordAudio-D-NndM1X.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.94 kB 🟢 -4.42 kB
assets/Load3D-BpfAUfRj.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.52 kB
assets/Load3D-CxZgtcTm.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.52 kB
assets/load3d-BeVeJdqG.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.19 kB 🟢 -3.63 kB
assets/load3d-fLPom6Np.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.64 kB
assets/AudioPreviewPlayer-CBdKQAOT.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.19 kB 🟢 -2.85 kB
assets/AudioPreviewPlayer-GN95Bmlg.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.19 kB 🔴 +2.85 kB
assets/changeTracker-BDYvmlKX.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.89 kB 🟢 -2.54 kB
assets/changeTracker-D9mzmkFc.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.89 kB 🔴 +2.54 kB
assets/nodeTemplates-cStTItAO.js (new) 9.29 kB 🔴 +9.29 kB 🔴 +3.25 kB 🔴 +2.86 kB
assets/nodeTemplates-DX_9R3jY.js (removed) 9.29 kB 🟢 -9.29 kB 🟢 -3.26 kB 🟢 -2.86 kB
assets/InviteMemberDialogContent-BcAg46HA.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.29 kB 🟢 -2 kB
assets/InviteMemberDialogContent-CpTOBc4W.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +1.99 kB
assets/Load3DConfiguration-_KXDeVtm.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/Load3DConfiguration-BXWSUViH.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/CreateWorkspaceDialogContent-CEXeKv5f.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/CreateWorkspaceDialogContent-DMx4wnaa.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.73 kB
assets/onboardingCloudRoutes-5gxIM22r.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.83 kB 🔴 +1.63 kB
assets/onboardingCloudRoutes-D6r0xGCL.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.84 kB 🟢 -1.61 kB
assets/FreeTierDialogContent-BqlBmzEa.js (new) 5.39 kB 🔴 +5.39 kB 🔴 +1.89 kB 🔴 +1.67 kB
assets/FreeTierDialogContent-CDcaXWiI.js (removed) 5.39 kB 🟢 -5.39 kB 🟢 -1.9 kB 🟢 -1.67 kB
assets/EditWorkspaceDialogContent-CAle-EBZ.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.94 kB 🟢 -1.7 kB
assets/EditWorkspaceDialogContent-mmPXe7WL.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.94 kB 🔴 +1.7 kB
assets/ValueControlPopover-0OXPVr61.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/ValueControlPopover-CJWPu7xb.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.76 kB 🟢 -1.57 kB
assets/Preview3d--xAZrB8O.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.36 kB
assets/Preview3d-BPGpSP1M.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/CancelSubscriptionDialogContent-BNM93cfS.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/CancelSubscriptionDialogContent-DQUaM8eP.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/DeleteWorkspaceDialogContent-BGQdBBI3.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.63 kB 🟢 -1.41 kB
assets/DeleteWorkspaceDialogContent-Bru5Zi7b.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/WidgetWithControl-DZqeDDRb.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.77 kB 🟢 -1.59 kB
assets/WidgetWithControl-laJv7Utv.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.77 kB 🔴 +1.59 kB
assets/LeaveWorkspaceDialogContent-111H58GH.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/LeaveWorkspaceDialogContent-BmHHqHWs.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.57 kB 🟢 -1.37 kB
assets/RemoveMemberDialogContent-CcPtPYXW.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.52 kB 🟢 -1.33 kB
assets/RemoveMemberDialogContent-CxzEPNEG.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/RevokeInviteDialogContent-Buz2iE6Q.js (removed) 3.95 kB 🟢 -3.95 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/RevokeInviteDialogContent-DZ7TG5XW.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/InviteMemberUpsellDialogContent-C_EeERUF.js (removed) 3.82 kB 🟢 -3.82 kB 🟢 -1.4 kB 🟢 -1.23 kB
assets/InviteMemberUpsellDialogContent-L8Sej7Ux.js (new) 3.82 kB 🔴 +3.82 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/tierBenefits-Cm1D0CYQ.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.3 kB 🟢 -1.17 kB
assets/tierBenefits-DPvWalra.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.3 kB 🔴 +1.17 kB
assets/saveMesh-BD7XUfSt.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/saveMesh-d5t4CCEK.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.45 kB 🟢 -1.28 kB
assets/cloudSessionCookie-DlbFTQop.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.08 kB 🔴 +980 B
assets/cloudSessionCookie-DS6HSkRR.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.08 kB 🟢 -979 B
assets/GlobalToast-CTpJ0cPL.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/GlobalToast-YGuEnkq4.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.03 kB
assets/SubscribeToRun-BW_kct9D.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -886 B
assets/SubscribeToRun-CRgYlLgb.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +880 B
assets/CloudRunButtonWrapper-D-Zub-wM.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -783 B 🟢 -707 B
assets/CloudRunButtonWrapper-yZBdEm_b.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +783 B 🔴 +715 B
assets/previousFullPath-0I_iVtM0.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +647 B 🔴 +575 B
assets/previousFullPath-C0t4wMgC.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -649 B 🟢 -576 B
assets/cloudBadges-C3ZwLpQ8.js (new) 1.36 kB 🔴 +1.36 kB 🔴 +699 B 🔴 +604 B
assets/cloudBadges-DasZixGD.js (removed) 1.36 kB 🟢 -1.36 kB 🟢 -699 B 🟢 -612 B
assets/cloudSubscription-B-IyB8f3.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -654 B 🟢 -563 B
assets/cloudSubscription-CSkcp3i0.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +653 B 🔴 +563 B
assets/Load3D-ClwVTziV.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +496 B 🔴 +440 B
assets/Load3D-DY5Cuk8d.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -496 B 🟢 -440 B
assets/nightlyBadges-D15e6xGg.js (removed) 1 kB 🟢 -1 kB 🟢 -529 B 🟢 -471 B
assets/nightlyBadges-Dgfksw2B.js (new) 1 kB 🔴 +1 kB 🔴 +530 B 🔴 +472 B
assets/Load3dViewerContent-BoUankTD.js (new) 993 B 🔴 +993 B 🔴 +466 B 🔴 +415 B
assets/Load3dViewerContent-BRrsmLxN.js (removed) 993 B 🟢 -993 B 🟢 -468 B 🟢 -415 B
assets/SubscriptionPanelContentWorkspace--IRlKmXi.js (removed) 920 B 🟢 -920 B 🟢 -436 B 🟢 -380 B
assets/SubscriptionPanelContentWorkspace-DHRFcJBx.js (new) 920 B 🔴 +920 B 🔴 +437 B 🔴 +377 B
assets/graphHasMissingNodes-CB9uVlOV.js (removed) 761 B 🟢 -761 B 🟢 -374 B 🟢 -333 B
assets/graphHasMissingNodes-xH4o484z.js (new) 761 B 🔴 +761 B 🔴 +375 B 🔴 +319 B
assets/changeTracker-CfaYQL2v.js (new) 757 B 🔴 +757 B 🔴 +385 B 🔴 +337 B
assets/changeTracker-rD6_d42S.js (removed) 757 B 🟢 -757 B 🟢 -385 B 🟢 -363 B
assets/WidgetLegacy-CecWiPwS.js (new) 745 B 🔴 +745 B 🔴 +381 B 🔴 +332 B
assets/WidgetLegacy-Y1nIpWgg.js (removed) 745 B 🟢 -745 B 🟢 -384 B 🟢 -359 B
assets/WidgetInputNumber-BoafSitW.js (removed) 469 B 🟢 -469 B 🟢 -262 B 🟢 -229 B
assets/WidgetInputNumber-nPH108eB.js (new) 469 B 🔴 +469 B 🔴 +262 B 🔴 +231 B
assets/AnimationControls-wxmm67Zx.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-YgivRjmv.js 2.69 kB 2.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-DQKI7wOs.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-D9MrYETV.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CuEodz4y.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B-AdR9IA.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CbkxT8K8.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CJGmjcIS.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CMaLgTTb.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Cw07MMbJ.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D7EtdE6o.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DknEFpK3.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Ds6WuXnw.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dvq-F-mb.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-pUOay9Eo.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-u2AZ8xU4.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-htt0vt7m.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-CJn1Ppc4.js 533 kB 533 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-DuR49qXA.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-9hDdrYl6.js 156 kB 156 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BCxyPdDP.js 148 kB 148 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bvh07s5y.js 186 kB 186 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C2cSZVv4.js 130 kB 130 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-C7AXDyHB.js 179 kB 179 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CCfW6OsY.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CIUvAJ2X.js 208 kB 208 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CoeFC4KG.js 149 kB 149 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CuCSlYvh.js 171 kB 171 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CWzA8F-K.js 131 kB 131 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D6ntfRCO.js 153 kB 153 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-Dqa2c7nZ.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-DLiWNcHw.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-BLQErkwF.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-NQGNpa4H.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-0crUoXWV.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-CbM1Hg4R.js 2.77 kB 2.77 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C65EmrE8.js 449 kB 449 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C8Y2lLDs.js 390 kB 390 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cd21bOuN.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CFVNYgsZ.js 415 kB 415 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CLQAr6Rt.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CWAeDqZ0.js 490 kB 490 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D8p6fLf4.js 395 kB 395 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DoD_2GTf.js 362 kB 362 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DpF2P1me.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DwWCG4ag.js 366 kB 366 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-K2c3p32c.js 449 kB 449 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-7ntlzwe2.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-C30NXhYp.js 8.94 kB 8.94 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-ClEZU89k.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-DOON_1XA.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-YFnxAfbN.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-DVSfLULk.js 2.01 kB 2.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-DNwi9WvU.js 1.35 kB 1.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-NeEr3XWN.js 586 B 586 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-B-n_WsWm.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-CrYkAmUC.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-BtoXUSiF.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-DCBI_-rd.js 2.9 kB 2.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-CIcV8pqy.js 9.36 kB 9.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-DZSYhGzO.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-DZgPHBtC.js 7 kB 7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-DEze40qY.js 1.86 kB 1.86 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-aaFXOFZ9.js 1.98 kB 1.98 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-Cqdttdn1.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-DN03zIgB.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-DXeEXWjl.js 3.96 kB 3.96 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-BDo6bJhB.js 6.8 kB 6.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-Br_tbhcL.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 52 added / 52 removed

@github-actions
Copy link

github-actions bot commented Feb 27, 2026

⚡ Performance Report

Metric Baseline PR Δ
canvas-idle: style recalcs 124 123 -1% ⚪
canvas-idle: layouts 0 0 +0% ⚪
canvas-idle: task duration 419ms 424ms +1% ⚪
canvas-mouse-sweep: style recalcs 182 181 -1% ⚪
canvas-mouse-sweep: layouts 12 12 +0% ⚪
canvas-mouse-sweep: task duration 918ms 1033ms +12% 🟠
dom-widget-clipping: style recalcs 44 43 -2% ⚪
dom-widget-clipping: layouts 0 0 +0% ⚪
dom-widget-clipping: task duration 369ms 363ms -2% ⚪
Raw data
{
  "timestamp": "2026-02-28T21:40:29.956Z",
  "gitSha": "353ffab64eb2ac67619db051f898a78a006e6b50",
  "branch": "drjkl/preview-fix",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2021.3099999999997,
      "styleRecalcs": 123,
      "styleRecalcDurationMs": 19.648,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 423.928,
      "heapDeltaBytes": -2584984
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2016.7120000000125,
      "styleRecalcs": 181,
      "styleRecalcDurationMs": 55.801,
      "layouts": 12,
      "layoutDurationMs": 3.172,
      "taskDurationMs": 1032.649,
      "heapDeltaBytes": -3854088
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 581.1559999999929,
      "styleRecalcs": 43,
      "styleRecalcDurationMs": 13.901999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 362.695,
      "heapDeltaBytes": 7622124
    }
  ]
}

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 (1)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

174-176: ⚠️ Potential issue | 🟠 Major

Guard reconciliation writes until the node has a real id.

Line 175 can still persist entries for this.id === -1 when widgets is read pre-add, which bypasses the deferred promotion flow and can leave orphaned promotions.

🐛 Proposed fix
-    if (shouldPersistLinkedOnly && hasChanged) {
+    if (this.id !== -1 && shouldPersistLinkedOnly && hasChanged) {
       store.setPromotions(this.rootGraph.id, this.id, mergedEntries)
     }
🤖 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 174 - 176, The
code currently calls store.setPromotions(this.rootGraph.id, this.id,
mergedEntries) when shouldPersistLinkedOnly && hasChanged, which can persist
promotions for a sentinel id (-1) if widgets were read before the node was
added; update the guard to also require the node has a real id (e.g., this.id
!== -1) before calling store.setPromotions (or otherwise defer the write until
after the node is assigned a real id), so change the conditional around the
setPromotions call in SubgraphNode (referencing shouldPersistLinkedOnly,
hasChanged, this.id, store.setPromotions, mergedEntries, this.rootGraph.id) to
prevent orphaned promotions.
🧹 Nitpick comments (2)
src/composables/graph/useGraphNodeManager.ts (1)

290-306: Verify the graph ID parameter usage.

Line 294 passes an empty string when node is not a subgraph node. Based on the function signature _graphId: string (underscore prefix), this parameter appears unused, but consider adding a comment explaining this or using a more explicit sentinel value.

The resolution result handling correctly extracts the resolved source with proper fallback to the original widget.

Suggestion: Add clarifying comment
       const resolvedSourceResult =
         isPromotedWidgetView(widget) && promotedSourceSeed
           ? resolveConcretePromotedWidget(
               node,
-              node.isSubgraphNode() ? node.rootGraph.id : '',
+              // Graph ID parameter is currently unused by the resolver
+              node.isSubgraphNode() ? node.rootGraph.id : '',
               promotedSourceSeed.sourceNodeId,
               promotedSourceSeed.sourceWidgetName
             )
           : null
🤖 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 290 - 306, The
call to resolveConcretePromotedWidget passes node.rootGraph.id when
node.isSubgraphNode() is true but an empty string otherwise; clarify intent by
adding a brief comment next to this call explaining that the _graphId parameter
is intentionally an unused/placeholder value for non-subgraph nodes (or note
that resolveConcretePromotedWidget treats empty string as a sentinel),
referencing resolveConcretePromotedWidget, node.isSubgraphNode(), and
node.rootGraph.id so future readers know this is deliberate and not a bug.
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

144-152: Use linear-time dedup in linked entry collection.

This dedup runs in a hot getter path and is currently O(n²) (findIndex inside filter). A Set-based key check will be simpler and scale better.

♻️ Proposed refactor
-    return linkedEntries.filter((entry, index, array) => {
-      const firstIndex = array.findIndex(
-        (candidate) =>
-          candidate.inputName === entry.inputName &&
-          candidate.interiorNodeId === entry.interiorNodeId &&
-          candidate.widgetName === entry.widgetName
-      )
-      return firstIndex === index
-    })
+    const seen = new Set<string>()
+    const deduped: Array<{
+      inputName: string
+      interiorNodeId: string
+      widgetName: string
+    }> = []
+
+    for (const entry of linkedEntries) {
+      const key = this._makePromotionViewKey(
+        entry.inputName,
+        entry.interiorNodeId,
+        entry.widgetName
+      )
+      if (seen.has(key)) continue
+      seen.add(key)
+      deduped.push(entry)
+    }
+
+    return deduped
🤖 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 144 - 152, The
current O(n²) dedup using linkedEntries.filter with array.findIndex should be
replaced with a linear-time Set-based dedup: iterate linkedEntries once, build a
Set of composite keys (e.g.
`${entry.inputName}:${entry.interiorNodeId}:${entry.widgetName}`) and push each
entry to the result array only if its key is not already in the Set; update the
code at the linkedEntries.filter location (the dedup block referencing
entry.inputName, entry.interiorNodeId, entry.widgetName) to perform this
single-pass Set check and return the result array.
🤖 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/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 174-176: The code currently calls
store.setPromotions(this.rootGraph.id, this.id, mergedEntries) when
shouldPersistLinkedOnly && hasChanged, which can persist promotions for a
sentinel id (-1) if widgets were read before the node was added; update the
guard to also require the node has a real id (e.g., this.id !== -1) before
calling store.setPromotions (or otherwise defer the write until after the node
is assigned a real id), so change the conditional around the setPromotions call
in SubgraphNode (referencing shouldPersistLinkedOnly, hasChanged, this.id,
store.setPromotions, mergedEntries, this.rootGraph.id) to prevent orphaned
promotions.

---

Nitpick comments:
In `@src/composables/graph/useGraphNodeManager.ts`:
- Around line 290-306: The call to resolveConcretePromotedWidget passes
node.rootGraph.id when node.isSubgraphNode() is true but an empty string
otherwise; clarify intent by adding a brief comment next to this call explaining
that the _graphId parameter is intentionally an unused/placeholder value for
non-subgraph nodes (or note that resolveConcretePromotedWidget treats empty
string as a sentinel), referencing resolveConcretePromotedWidget,
node.isSubgraphNode(), and node.rootGraph.id so future readers know this is
deliberate and not a bug.

In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 144-152: The current O(n²) dedup using linkedEntries.filter with
array.findIndex should be replaced with a linear-time Set-based dedup: iterate
linkedEntries once, build a Set of composite keys (e.g.
`${entry.inputName}:${entry.interiorNodeId}:${entry.widgetName}`) and push each
entry to the result array only if its key is not already in the Set; update the
code at the linkedEntries.filter location (the dedup block referencing
entry.inputName, entry.interiorNodeId, entry.widgetName) to perform this
single-pass Set check and return the result array.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0db3e and 25afb54.

📒 Files selected for processing (2)
  • src/composables/graph/useGraphNodeManager.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts

…aversal

- Extract resolveSubgraphInputLink helper to deduplicate link traversal
- Unify resolveConcretePromotedWidget into traversePromotedWidgetChain
- Use discriminated union (status field) for resolution results
- Remove unused _graphId parameter from resolution functions
- Add max-depth guard for promoted widget chain traversal
- Use value-based cache keys in PromotedWidgetViewManager
- Extract _syncPromotions, _removePromotedView helpers in SubgraphNode
- Use design tokens for disconnected placeholder rendering
- Add unit tests for resolveConcretePromotedWidget

Amp-Thread-ID: https://ampcode.com/threads/T-019ca0a1-e047-75b9-9583-2f08a0efffe1
Co-authored-by: Amp <amp@ampcode.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/core/graph/subgraph/resolveConcretePromotedWidget.ts (1)

25-35: ⚠️ Potential issue | 🟠 Major

Cycle detection key is not host-identity-safe.

Line 31 keys visits by currentHost.id, which can collide across different nested hosts and trigger false 'cycle' failures in valid traversals.

Suggested fix
 function traversePromotedWidgetChain(
   hostNode: SubgraphNode,
   nodeId: string,
   widgetName: string
 ): PromotedWidgetResolutionResult {
   const visited = new Set<string>()
+  const hostUidMap = new WeakMap<SubgraphNode, number>()
+  let nextHostUid = 0
   let currentHost = hostNode
   let currentNodeId = nodeId
   let currentWidgetName = widgetName

   for (let depth = 0; depth < MAX_PROMOTED_WIDGET_CHAIN_DEPTH; depth++) {
-    const key = `${currentHost.id}:${currentNodeId}:${currentWidgetName}`
+    let hostUid = hostUidMap.get(currentHost)
+    if (hostUid === undefined) {
+      hostUid = nextHostUid++
+      hostUidMap.set(currentHost, hostUid)
+    }
+    const key = `${hostUid}:${currentNodeId}:${currentWidgetName}`
     if (visited.has(key)) {
       return { status: 'failure', failure: 'cycle' }
     }
     visited.add(key)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/graph/subgraph/resolveConcretePromotedWidget.ts` around lines 25 -
35, The cycle-detection key currently uses currentHost.id which can collide
across different host objects; replace the stringified key construction by
deriving a host-identity that is stable and unique per host object (not its .id
property) — e.g. create a WeakMap<HostNode, number> (or a Map with generated
symbol ids) to assign each host object a unique numeric id and use that
hostIdentity in the key; update the key construction (used with visited Set in
resolveConcretePromotedWidget) from
`${currentHost.id}:${currentNodeId}:${currentWidgetName}` to something like
`${hostIdentity}:${currentNodeId}:${currentWidgetName}`, ensuring the WeakMap
lookup/assignment happens outside or just before the loop so different host
instances cannot collide.
🧹 Nitpick comments (2)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (2)

199-199: Type precision: undefined key appears unused.

The return type Map<string | undefined, string> suggests undefined keys are valid, but _buildDisplayNameByViewKey always produces string keys via _makePromotionViewKey. Consider tightening to Map<string, string> for clarity.

♻️ Proposed type refinement
-    displayNameByViewKey: Map<string | undefined, string>
+    displayNameByViewKey: Map<string, string>
🤖 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` at line 199, The Map type for
displayNameByViewKey is too permissive—change its declaration from Map<string |
undefined, string> to Map<string, string> and update any declarations/returns in
SubgraphNode where this map is constructed (notably _buildDisplayNameByViewKey)
to reflect that _makePromotionViewKey always returns a string; ensure callers of
displayNameByViewKey and the return type of _buildDisplayNameByViewKey (and any
related signatures) accept Map<string, string> so types remain consistent across
SubgraphNode and its consumers.

128-136: Side effect in collection method.

Lines 132-133 mutate input.widget while collecting linked entries. This side effect in a method that appears to be a pure collector could cause subtle issues if called during different lifecycle phases. Consider extracting the widget initialization to a dedicated sync method, or document that this method has side effects.

🤖 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 128 - 136, The
loop that builds linkedEntries mutates input.widget (lines with input.widget ??=
and input.widget.name =), causing side effects during what should be a pure
collection; remove those two mutations from the collector and extract them into
a new method on SubgraphNode (e.g., initializeInputWidgets or
ensureInputWidgets) which iterates this.inputs and sets widget = { name:
input.name } and widget.name = input.name when missing; update call sites to
invoke initializeInputWidgets at the appropriate lifecycle points before the
collector runs (or, if mutation must remain, add a clear comment on the
collector method documenting the side effect) and keep references to
_resolveLinkedPromotionByInputName and linkedEntries unchanged.
🤖 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/composables/graph/useGraphNodeManager.ts`:
- Around line 391-402: The reactive computed getter for safeWidgets repopulates
the persistent Map slotMetadata without clearing it, causing stale entries when
node.inputs change; modify the safeWidgets computation (inside the
reactiveComputed used to produce safeWidgets) to call slotMetadata.clear()
before iterating node.inputs and repopulating it so slotMetadata only contains
current input/widget entries used by safeWidgetMapper.

In `@src/core/graph/subgraph/resolveSubgraphInputLink.ts`:
- Around line 30-31: The code in resolveSubgraphInputLink is assuming
inputNode.inputs exists before calling find, which can throw on partial/stale
graphs; update the logic around the inputNode and inputs lookup (where
targetInput is assigned from inputNode.inputs.find with linkId) to first check
that inputNode is defined and that inputNode.inputs is an Array (e.g.
Array.isArray(inputNode.inputs)) before calling find, and if not present simply
continue the loop or handle the missing inputs case the same way it previously
handled a missing targetInput so resolution doesn't short-circuit.

---

Duplicate comments:
In `@src/core/graph/subgraph/resolveConcretePromotedWidget.ts`:
- Around line 25-35: The cycle-detection key currently uses currentHost.id which
can collide across different host objects; replace the stringified key
construction by deriving a host-identity that is stable and unique per host
object (not its .id property) — e.g. create a WeakMap<HostNode, number> (or a
Map with generated symbol ids) to assign each host object a unique numeric id
and use that hostIdentity in the key; update the key construction (used with
visited Set in resolveConcretePromotedWidget) from
`${currentHost.id}:${currentNodeId}:${currentWidgetName}` to something like
`${hostIdentity}:${currentNodeId}:${currentWidgetName}`, ensuring the WeakMap
lookup/assignment happens outside or just before the loop so different host
instances cannot collide.

---

Nitpick comments:
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Line 199: The Map type for displayNameByViewKey is too permissive—change its
declaration from Map<string | undefined, string> to Map<string, string> and
update any declarations/returns in SubgraphNode where this map is constructed
(notably _buildDisplayNameByViewKey) to reflect that _makePromotionViewKey
always returns a string; ensure callers of displayNameByViewKey and the return
type of _buildDisplayNameByViewKey (and any related signatures) accept
Map<string, string> so types remain consistent across SubgraphNode and its
consumers.
- Around line 128-136: The loop that builds linkedEntries mutates input.widget
(lines with input.widget ??= and input.widget.name =), causing side effects
during what should be a pure collection; remove those two mutations from the
collector and extract them into a new method on SubgraphNode (e.g.,
initializeInputWidgets or ensureInputWidgets) which iterates this.inputs and
sets widget = { name: input.name } and widget.name = input.name when missing;
update call sites to invoke initializeInputWidgets at the appropriate lifecycle
points before the collector runs (or, if mutation must remain, add a clear
comment on the collector method documenting the side effect) and keep references
to _resolveLinkedPromotionByInputName and linkedEntries unchanged.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25afb54 and 6ac2cfb.

📒 Files selected for processing (10)
  • src/composables/graph/useGraphNodeManager.ts
  • src/core/graph/subgraph/promotedWidgetTypes.ts
  • src/core/graph/subgraph/promotedWidgetView.test.ts
  • src/core/graph/subgraph/promotedWidgetView.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.test.ts
  • src/core/graph/subgraph/resolveConcretePromotedWidget.ts
  • src/core/graph/subgraph/resolvePromotedWidgetSource.ts
  • src/core/graph/subgraph/resolveSubgraphInputLink.ts
  • src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts

DrJKL and others added 2 commits February 27, 2026 12:15
PromotedWidgetView.computedDisabled always returned false and ignored
updates from updateComputedDisabled(), so externally linked slots were
never visually disabled.

DomWidget.vue read computedDisabled from the original inner widget
(always true because it's linked to SubgraphInput) instead of the
PromotedWidgetView that owns the position override.

- Store computedDisabled in PromotedWidgetView so linked slots disable
- DomWidget.vue reads from positionOverride.widget when overridden
- Add nested promotion E2E test with fixture

Amp-Thread-ID: https://ampcode.com/threads/T-019ca0b0-4e18-73e8-8d2d-6f2a0ee273be
Co-authored-by: Amp <amp@ampcode.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
browser_tests/tests/subgraphPromotion.spec.ts (1)

594-623: Consider using a more robust disabled detection.

The test relies on wrapper opacity to determine if a textarea is editable. While this works, it's coupled to the specific CSS implementation of the disabled state.

Consider checking the disabled attribute or readonly property of the textarea directly, or asserting against the known widget names that should be editable based on the test fixture:

// Alternative: Check for disabled attribute directly
const isDisabled = await textarea.evaluate((el) => 
  (el as HTMLTextAreaElement).disabled || (el as HTMLTextAreaElement).readOnly
)

That said, if the opacity styling is the intended UX indicator and this test is specifically validating that UX, the current approach is acceptable.

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

In `@browser_tests/tests/subgraphPromotion.spec.ts` around lines 594 - 623, The
test "Unlinked promoted textarea widgets are editable on the subgraph exterior"
currently infers editability by reading the wrapper's CSS opacity; replace that
brittle check with a direct DOM property check on each textarea (use the
existing textareas and textarea variables) by evaluating the element's disabled
and readOnly properties and only attempt fill/assert when those properties are
false; update the loop around textareas.nth(i) and the conditional that uses
opacity to instead call textarea.evaluate to read (el as
HTMLTextAreaElement).disabled || (el as HTMLTextAreaElement).readOnly and
proceed when that result is false so the test asserts real editability rather
than a visual style.
🤖 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/graph/widgets/DomWidget.vue`:
- Around line 113-123: The override branch currently treats
override.widget.computedDisabled as false when undefined, which can re-enable
interactions; change the expression that computes isDisabled so that when
override exists you fall back to the base widget's computedDisabled: replace the
logic in the widgetState/override block (where isDisabled is computed) to use
override ? (override.widget.computedDisabled ?? widget.computedDisabled) :
widget.computedDisabled so pointerEvents and opacity continue to derive from the
correct disabled source.

---

Nitpick comments:
In `@browser_tests/tests/subgraphPromotion.spec.ts`:
- Around line 594-623: The test "Unlinked promoted textarea widgets are editable
on the subgraph exterior" currently infers editability by reading the wrapper's
CSS opacity; replace that brittle check with a direct DOM property check on each
textarea (use the existing textareas and textarea variables) by evaluating the
element's disabled and readOnly properties and only attempt fill/assert when
those properties are false; update the loop around textareas.nth(i) and the
conditional that uses opacity to instead call textarea.evaluate to read (el as
HTMLTextAreaElement).disabled || (el as HTMLTextAreaElement).readOnly and
proceed when that result is false so the test asserts real editability rather
than a visual style.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac2cfb and 4e2c244.

📒 Files selected for processing (4)
  • browser_tests/assets/subgraphs/subgraph-nested-promotion.json
  • browser_tests/tests/subgraphPromotion.spec.ts
  • src/components/graph/widgets/DomWidget.vue
  • src/core/graph/subgraph/promotedWidgetView.ts

DrJKL and others added 4 commits February 27, 2026 12:25
Prevents stale entries from accumulating when node.inputs change, which could cause safeWidgetMapper to return incorrect slot metadata.

Amp-Thread-ID: https://ampcode.com/threads/T-019ca0c1-d0be-76f6-9ee3-2d7a13a86fb5
Co-authored-by: Amp <amp@ampcode.com>
When override.widget.computedDisabled is undefined, fall back to the base widget's computedDisabled instead of false to prevent re-enabling disabled widgets.

Amp-Thread-ID: https://ampcode.com/threads/T-019ca0c1-d0be-76f6-9ee3-2d7a13a86fb5
Co-authored-by: Amp <amp@ampcode.com>
The setter is not a no-op; it accepts boolean values and treats undefined as false. Split into two tests covering both behaviors.

Amp-Thread-ID: https://ampcode.com/threads/T-019ca0c1-d0be-76f6-9ee3-2d7a13a86fb5
Co-authored-by: Amp <amp@ampcode.com>
@DrJKL DrJKL marked this pull request as ready for review February 27, 2026 22:26
@DrJKL DrJKL requested a review from a team as a code owner February 27, 2026 22:26
@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Feb 27, 2026
forEachNode(node.subgraph, (innerNode) => {
innerNode.onRemoved?.()
innerNode.graph?.onNodeRemoved?.(innerNode)
})
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (non-blocking): when the last reference to a subgraph node is removed, the old code also deleted rootGraph.subgraphs entries for nested SubgraphNodes via forEachNode. That recursive cleanup was dropped here, so nested subgraph definitions remain orphaned in rootGraph.subgraphs until the workflow is reloaded.

Serialization already filters by usedSubgraphIds, so this isn't a correctness bug, but it is a session-lifetime memory leak for workflows that repeatedly create/remove deeply nested subgraphs, and the allGraphs scan in this same method grows monotonically.

I realize there's a lot of context and recent PRs around this area, and this is likely an accepted tradeoff for this PR to fix the immediate bugs. For the long term though, we need a better architecture for subgraph definition lifecycle management - probably something that reconciles definitions centrally rather than relying on node removal callbacks. Worth tracking as a follow-up.

widgetName: string,
viewKey?: string
): string {
return viewKey ?? `${interiorNodeId}:${widgetName}`
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (non-blocking): makeKey returns viewKey ?? \${interiorNodeId}:${widgetName}`- whenviewKeyis provided,interiorNodeIdandwidgetName` are completely ignored. This means:

  1. remove(id, name) generates ${id}:${name} but cannot find entries stored under a viewKey
  2. _removePromotedView works around this by calling both remove() and removeByViewKey(), but that's a fragile implicit contract
  3. During reconcile's stale-key cleanup (line 54-56), entries stored under a viewKey cannot be evicted by their (id, name) pair

Consider either incorporating interiorNodeId:widgetName as a prefix in the viewKey path, or renaming the parameter to keyOverride and documenting the dual-keying contract explicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There once was a key with no base,
That wandered all over the place.
Now node-widget leads,
Then viewKey succeeds,
And cache ops all run in one space.

y: number,
H: number
) {
const backgroundColor = readDesignToken(
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): readDesignToken calls getComputedStyle(document.documentElement).getPropertyValue(token) four times per drawDisconnectedPlaceholder invocation. This is on the canvas draw path, so for N disconnected promoted widgets it's 4N getComputedStyle calls per frame. getComputedStyle can force style recalculation.

Since these CSS custom properties only change on theme switch, consider caching the values at module level - e.g. a lazy let cachedTokens: Record<string, string> | null that's populated on first call and invalidated via a matchMedia listener or theme-change event.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A placeholder painted each frame,
With style reads again and again.
Now token values stay,
In a cache by the way,
So redraws are far less of a strain.

private _getPromotedViews(): PromotedWidgetView[] {
const store = usePromotionStore()
const entries = store.getPromotionsRef(this.rootGraph.id, this.id)
const linkedEntries = this._getLinkedPromotionEntries()
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): _getPromotedViews() calls _getLinkedPromotionEntries() on every widgets getter access. That method iterates all inputs, resolves links via resolveSubgraphInputLink (which does .find() on slots, iterates linkIds, resolves links, and does another .find() on inner inputs), then deduplicates. The widgets getter is a very hot path - rendering, layout, serialization all hit it.

While PromotedWidgetViewManager.reconcile() caches the view objects, the entry key computation and link resolution preceding it runs unconditionally every time. Consider caching _getLinkedPromotionEntries() and invalidating only when inputs/links actually change (via the existing input-connected/input-disconnected/removing-input event handlers).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A getter kept chasing each link,
On every tiny re-think.
Now entries are cached,
With invalidates matched,
So hot paths no longer must clink.

}
}

function resolveSourceSeedByInputName(inputName: string): {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: resolveSourceSeedByInputName is a near-exact copy of SubgraphNode._resolveLinkedPromotionByInputName (same branching logic, same resolveSubgraphInputLink call, just different field names: sourceNodeId vs interiorNodeId). This is change amplification - any future change to resolution semantics must be updated in both places.

Consider extracting a shared resolver function in resolveSubgraphInputLink.ts that both call sites use.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Two resolvers marched side by side,
With branching and checks duplicated wide.
Now one helper knows,
Where the true target goes,
And both callers use that one guide.

hostNode: LGraphNode,
widget: IBaseWidget
): ResolvedPromotedWidgetSource | undefined {
): ResolvedPromotedWidget | undefined {
Copy link
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): resolvePromotedWidgetSource now resolves through nested promoted widget chains (via resolveConcretePromotedWidget) instead of only resolving the immediate source. Extensions calling this function may now receive a deeper interior node/widget pair than before. The structural type shape is unchanged, but the behavioral contract is different. If any extension needs the shallow (one-level) resolution, they should use the new resolvePromotedWidgetAtHost export.

expect(renderedText).toContain('a')
})

test('draw shows value through two input-based promotion layers', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick (non-blocking): the nested promotion tests ("draw shows value through two...", "value updates propagate...", "state lookup resolves...", etc.) repeat nearly identical 15-line setup blocks to create a two-level nested subgraph. Extracting a shared createTwoLevelNestedSubgraph() helper would reduce setup noise and make the unique assertion in each test stand out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nested dawn once cloned,
One helper now sets the stage,
Assertions stand bright.


const subgraphNodeB = createTestSubgraphNode(subgraphB, { id: 22 })
const widgetValueStore = useWidgetValueStore()
const getWidgetSpy = vi.spyOn(widgetValueStore, 'getWidget')
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick (non-blocking): this test uses vi.spyOn(widgetValueStore, 'getWidget') to assert that getWidget was called with the concrete inner node's ID. This is a change-detector test - if the implementation changes how it retrieves widget state (caching, batching), this breaks even though behavior is correct. The value propagation tests nearby already verify the same semantics more robustly by checking actual 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.

Spyglass set aside,
Behavior sings what is true,
Value proves the path.

import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'

type MockGraphNode = {
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick (non-blocking): these tests use hand-rolled MockGraphNode/MockSubgraph types instead of the createTestSubgraph/createTestSubgraphNode helpers used elsewhere. This keeps tests fast but creates a divergence risk - if SubgraphNode gains new properties that affect isPromotedWidgetView or isSubgraphNode checks, these mocks won't catch regressions. Consider adding at least one integration-level test using real fixtures (the nested promotion tests in promotedWidgetView.test.ts may already cover this).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixtures replace stage props.
Integration texture enters the test grain.
X-ray mocks give way to living subgraph bones.
The helpers (createTestSubgraph, createTestSubgraphNode) anchor reality.
Under drift risk, coverage hardens.
Regression now meets a truer mirror.
Edges hold.


test('Can drag node', { tag: '@screenshot' }, async ({ comfyPage }) => {
await comfyPage.nodeOps.dragTextEncodeNode2()
await comfyPage.nextFrame()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a stabilization fix for the test, the DOM widget z-index is inconsistent at point of screenshot otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be worth coming back and adding a proper wait condition later.

@DrJKL DrJKL assigned christian-byrne and unassigned DrJKL Feb 28, 2026
@DrJKL DrJKL merged commit dd1a1f7 into main Feb 28, 2026
33 checks passed
@DrJKL DrJKL deleted the drjkl/preview-fix branch February 28, 2026 21:45
DrJKL added a commit that referenced this pull request Feb 28, 2026
Fix multiple issues with promoted widget resolution in nested subgraphs,
ensuring correct value propagation, slot matching, and rendering for
deeply nested promoted widgets.

- **What**: Stabilize nested subgraph promoted widget resolution chain
- Use deep source keys for promoted widget values in Vue rendering mode
- Resolve effective widget options from the source widget instead of the
promoted view
  - Stabilize slot resolution for nested promoted widgets
  - Preserve combo value rendering for promoted subgraph widgets
- Prevent subgraph definition deletion while other nodes still reference
the same type
  - Clean up unused exported resolution types

- `resolveConcretePromotedWidget.ts` — new recursive resolution logic
for deeply nested promoted widgets
- `useGraphNodeManager.ts` — option extraction now uses
`effectiveWidget` for promoted widgets
- `SubgraphNode.ts` — unpack no longer force-deletes definitions
referenced by other nodes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9282-fix-stabilize-nested-subgraph-promoted-widget-resolution-3146d73d365081208a4fe931bb7569cf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
@DrJKL DrJKL added needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch cloud/1.40 Backport PRs for cloud 1.40 labels Mar 5, 2026
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

⚠️ Backport to cloud/1.40 failed

Reason: Merge conflicts detected during cherry-pick of dd1a1f7

📄 Conflicting files
browser_tests/tests/subgraphPromotion.spec.ts
src/composables/graph/useGraphNodeManager.test.ts
src/composables/graph/useGraphNodeManager.ts
src/core/graph/subgraph/promotedWidgetTypes.ts
src/core/graph/subgraph/promotedWidgetView.test.ts
src/core/graph/subgraph/promotedWidgetView.ts
src/core/graph/subgraph/resolvePromotedWidgetSource.ts
src/lib/litegraph/src/LGraph.test.ts
src/lib/litegraph/src/LGraph.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
src/lib/litegraph/src/subgraph/SubgraphNode.ts
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts
🤖 Prompt for AI Agents
Backport PR #9282 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282) to cloud/1.40.
Cherry-pick merge commit dd1a1f77d643d7e341b420e8bce583e28506acd4 onto new branch
backport-9282-to-cloud-1.40 from origin/cloud/1.40.
Resolve conflicts in: browser_tests/tests/subgraphPromotion.spec.ts src/composables/graph/useGraphNodeManager.test.ts src/composables/graph/useGraphNodeManager.ts src/core/graph/subgraph/promotedWidgetTypes.ts src/core/graph/subgraph/promotedWidgetView.test.ts src/core/graph/subgraph/promotedWidgetView.ts src/core/graph/subgraph/resolvePromotedWidgetSource.ts src/lib/litegraph/src/LGraph.test.ts src/lib/litegraph/src/LGraph.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts src/lib/litegraph/src/subgraph/SubgraphNode.ts src/renderer/extensions/vueNodes/components/NodeWidgets.vue src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.40] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

1 similar comment
@github-actions
Copy link

github-actions bot commented Mar 5, 2026

⚠️ Backport to cloud/1.40 failed

Reason: Merge conflicts detected during cherry-pick of dd1a1f7

📄 Conflicting files
browser_tests/tests/subgraphPromotion.spec.ts
src/composables/graph/useGraphNodeManager.test.ts
src/composables/graph/useGraphNodeManager.ts
src/core/graph/subgraph/promotedWidgetTypes.ts
src/core/graph/subgraph/promotedWidgetView.test.ts
src/core/graph/subgraph/promotedWidgetView.ts
src/core/graph/subgraph/resolvePromotedWidgetSource.ts
src/lib/litegraph/src/LGraph.test.ts
src/lib/litegraph/src/LGraph.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
src/lib/litegraph/src/subgraph/SubgraphNode.ts
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts
🤖 Prompt for AI Agents
Backport PR #9282 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282) to cloud/1.40.
Cherry-pick merge commit dd1a1f77d643d7e341b420e8bce583e28506acd4 onto new branch
backport-9282-to-cloud-1.40 from origin/cloud/1.40.
Resolve conflicts in: browser_tests/tests/subgraphPromotion.spec.ts src/composables/graph/useGraphNodeManager.test.ts src/composables/graph/useGraphNodeManager.ts src/core/graph/subgraph/promotedWidgetTypes.ts src/core/graph/subgraph/promotedWidgetView.test.ts src/core/graph/subgraph/promotedWidgetView.ts src/core/graph/subgraph/resolvePromotedWidgetSource.ts src/lib/litegraph/src/LGraph.test.ts src/lib/litegraph/src/LGraph.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts src/lib/litegraph/src/subgraph/SubgraphNode.ts src/renderer/extensions/vueNodes/components/NodeWidgets.vue src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.40] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

DrJKL added a commit that referenced this pull request Mar 5, 2026
DrJKL added a commit that referenced this pull request Mar 5, 2026
- Add svgBitmapCache.ts from #9172 (dependency of #9282)

- Update drawImage to use workflowBitmapCache

- Fix getWidget 3-arg signature in domWidget.ts and test

- Remove unconditional subgraphs.delete in unpackSubgraph

- Regenerate pnpm-lock.yaml

Amp-Thread-ID: https://ampcode.com/threads/T-019cbfdf-bf82-76de-b36c-91758530a0ce
Co-authored-by: Amp <amp@ampcode.com>
@christian-byrne christian-byrne added the core/1.40 Backport PRs for core 1.40 label Mar 8, 2026
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

⚠️ Backport to core/1.40 failed

Reason: Merge conflicts detected during cherry-pick of dd1a1f7

📄 Conflicting files
browser_tests/tests/subgraphPromotion.spec.ts
src/composables/graph/useGraphNodeManager.test.ts
src/composables/graph/useGraphNodeManager.ts
src/core/graph/subgraph/promotedWidgetTypes.ts
src/core/graph/subgraph/promotedWidgetView.test.ts
src/core/graph/subgraph/promotedWidgetView.ts
src/core/graph/subgraph/resolvePromotedWidgetSource.ts
src/lib/litegraph/src/LGraph.test.ts
src/lib/litegraph/src/LGraph.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
src/lib/litegraph/src/subgraph/SubgraphNode.ts
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts
🤖 Prompt for AI Agents
Backport PR #9282 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282) to core/1.40.
Cherry-pick merge commit dd1a1f77d643d7e341b420e8bce583e28506acd4 onto new branch
backport-9282-to-core-1.40 from origin/core/1.40.
Resolve conflicts in: browser_tests/tests/subgraphPromotion.spec.ts src/composables/graph/useGraphNodeManager.test.ts src/composables/graph/useGraphNodeManager.ts src/core/graph/subgraph/promotedWidgetTypes.ts src/core/graph/subgraph/promotedWidgetView.test.ts src/core/graph/subgraph/promotedWidgetView.ts src/core/graph/subgraph/resolvePromotedWidgetSource.ts src/lib/litegraph/src/LGraph.test.ts src/lib/litegraph/src/LGraph.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts src/lib/litegraph/src/subgraph/SubgraphNode.ts src/renderer/extensions/vueNodes/components/NodeWidgets.vue src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport core/1.40] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

⚠️ Backport to cloud/1.40 failed

Reason: Merge conflicts detected during cherry-pick of dd1a1f7

📄 Conflicting files
browser_tests/tests/subgraphPromotion.spec.ts
src/composables/graph/useGraphNodeManager.test.ts
src/composables/graph/useGraphNodeManager.ts
src/core/graph/subgraph/promotedWidgetTypes.ts
src/core/graph/subgraph/promotedWidgetView.test.ts
src/core/graph/subgraph/promotedWidgetView.ts
src/core/graph/subgraph/resolvePromotedWidgetSource.ts
src/lib/litegraph/src/LGraph.test.ts
src/lib/litegraph/src/LGraph.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts
src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts
src/lib/litegraph/src/subgraph/SubgraphNode.ts
src/renderer/extensions/vueNodes/components/NodeWidgets.vue
src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts
🤖 Prompt for AI Agents
Backport PR #9282 (https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282) to cloud/1.40.
Cherry-pick merge commit dd1a1f77d643d7e341b420e8bce583e28506acd4 onto new branch
backport-9282-to-cloud-1.40 from origin/cloud/1.40.
Resolve conflicts in: browser_tests/tests/subgraphPromotion.spec.ts src/composables/graph/useGraphNodeManager.test.ts src/composables/graph/useGraphNodeManager.ts src/core/graph/subgraph/promotedWidgetTypes.ts src/core/graph/subgraph/promotedWidgetView.test.ts src/core/graph/subgraph/promotedWidgetView.ts src/core/graph/subgraph/resolvePromotedWidgetSource.ts src/lib/litegraph/src/LGraph.test.ts src/lib/litegraph/src/LGraph.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.test.ts src/lib/litegraph/src/subgraph/PromotedWidgetViewManager.ts src/lib/litegraph/src/subgraph/SubgraphNode.ts src/renderer/extensions/vueNodes/components/NodeWidgets.vue src/renderer/extensions/vueNodes/widgets/utils/resolvePromotedWidget.test.ts .
For test snapshots (browser_tests/**/*-snapshots/), accept PR version if
changed in original PR, else keep target. For package.json versions, keep
target branch. For pnpm-lock.yaml, regenerate with pnpm install.
Ask user for non-obvious conflicts.
Create PR titled "[backport cloud/1.40] <original title>" with label "backport".
See .github/workflows/pr-backport.yaml for workflow details.

cc @DrJKL

christian-byrne pushed a commit that referenced this pull request Mar 8, 2026
Fix multiple issues with promoted widget resolution in nested subgraphs,
ensuring correct value propagation, slot matching, and rendering for
deeply nested promoted widgets.

- **What**: Stabilize nested subgraph promoted widget resolution chain
- Use deep source keys for promoted widget values in Vue rendering mode
- Resolve effective widget options from the source widget instead of the
promoted view
  - Stabilize slot resolution for nested promoted widgets
  - Preserve combo value rendering for promoted subgraph widgets
- Prevent subgraph definition deletion while other nodes still reference
the same type
  - Clean up unused exported resolution types

- `resolveConcretePromotedWidget.ts` — new recursive resolution logic
for deeply nested promoted widgets
- `useGraphNodeManager.ts` — option extraction now uses
`effectiveWidget` for promoted widgets
- `SubgraphNode.ts` — unpack no longer force-deletes definitions
referenced by other nodes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9282-fix-stabilize-nested-subgraph-promoted-widget-resolution-3146d73d365081208a4fe931bb7569cf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
christian-byrne added a commit that referenced this pull request Mar 8, 2026
…esolution (#9282) (#9616)

Backport of #9282 to core/1.40. MUST — user-facing subgraph widget
resolution bug.

12 conflict files resolved:
- 8 modify/delete: new files introduced by the PR (kept as new)
- 1 add/add: resolveSubgraphInputTarget.ts (merged with existing from
#9542 backport)
- 3 content: accepted incoming changes

**Original PR:** #9282
**Pipeline ticket:** 15e1f241-efaa-4fe5-88ca-4ccc7bfb3345

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cloud/1.40 Backport PRs for cloud 1.40 core/1.40 Backport PRs for core 1.40 needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants