Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d820cff
fix: preserve combo value rendering for promoted subgraph widgets
DrJKL Feb 26, 2026
42201a2
fix: stabilize nested subgraph promoted widget resolution
DrJKL Feb 27, 2026
32c88d3
fix: use deep source keys for promoted widget values in vue mode
DrJKL Feb 27, 2026
38419cb
fix: stabilize nested promoted widget slot resolution
DrJKL Feb 27, 2026
b7ef63d
fix: keep referenced subgraph definitions on unpack
DrJKL Feb 27, 2026
4c0db3e
fix: remove unused exported resolution types
DrJKL Feb 27, 2026
25afb54
fix: simplify promotion merge flow and widget naming
DrJKL Feb 27, 2026
6ac2cfb
refactor: unify promoted widget resolution and subgraph input link tr…
DrJKL Feb 27, 2026
9595fec
fix: promoted DOM widgets disabled state on subgraph exterior
DrJKL Feb 27, 2026
4e2c244
[automated] Apply ESLint and Oxfmt fixes
actions-user Feb 27, 2026
8690d82
fix: clear slotMetadata before repopulating in reactiveComputed
DrJKL Feb 27, 2026
41907c0
fix: fall back to base widget disabled state in DomWidget override
DrJKL Feb 27, 2026
f1eea82
test: fix computedDisabled test to match actual setter behavior
DrJKL Feb 27, 2026
1e57842
Fix: promoted test
DrJKL Feb 27, 2026
9895929
fix: add nextFrame sync before drag screenshot assertion
DrJKL Feb 27, 2026
835f5c8
fix: address prioritized PR9282 review items
DrJKL Feb 28, 2026
356e726
fix: make subgraph input target type internal
DrJKL Feb 28, 2026
ce1a32d
fix: restore live promoted widget resolution behavior
DrJKL Feb 28, 2026
38b088d
fix: resolve remaining Christian PR9282 comments
DrJKL Feb 28, 2026
31c1ead
fix: cache promoted widget resolution per frame
DrJKL Feb 28, 2026
c12fc97
test: use subgraph fixtures in promoted widget resolution tests
DrJKL Feb 28, 2026
2dbc101
fix: return undefined on failed promoted widget state lookup
DrJKL Feb 28, 2026
62f23d0
test: use subgraph fixtures in input link resolution tests
DrJKL Feb 28, 2026
4fa4642
Merge branch 'main' into drjkl/preview-fix
DrJKL Feb 28, 2026
62654c6
test: use dom widget store in DomWidget test setup
DrJKL Feb 28, 2026
54d117c
Merge branch 'main' into drjkl/preview-fix
DrJKL Feb 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
760 changes: 760 additions & 0 deletions browser_tests/assets/subgraphs/subgraph-nested-promotion.json

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions browser_tests/tests/subgraphPromotion.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,74 @@ test.describe(
})
})

test.describe('Nested Promoted Widget Disabled State', () => {
test('Externally linked promoted widget is disabled, unlinked ones are not', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow(
'subgraphs/subgraph-nested-promotion'
)
await comfyPage.nextFrame()

// Node 5 (Sub 0) has 4 promoted widgets. The first (string_a) has its
// slot connected externally from the Outer node, so it should be
// disabled. The remaining promoted textarea widgets (value, value_1)
// are unlinked and should be enabled.
const promotedNames = await getPromotedWidgetNames(comfyPage, '5')
expect(promotedNames).toContain('string_a')
expect(promotedNames).toContain('value')

const disabledState = await comfyPage.page.evaluate(() => {
const node = window.app!.canvas.graph!.getNodeById('5')
return (node?.widgets ?? []).map((w) => ({
name: w.name,
disabled: !!w.computedDisabled
}))
})

const linkedWidget = disabledState.find((w) => w.name === 'string_a')
expect(linkedWidget?.disabled).toBe(true)

const unlinkedWidgets = disabledState.filter(
(w) => w.name !== 'string_a'
)
for (const w of unlinkedWidgets) {
expect(w.disabled).toBe(false)
}
})

test('Unlinked promoted textarea widgets are editable on the subgraph exterior', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow(
'subgraphs/subgraph-nested-promotion'
)
await comfyPage.nextFrame()

// The promoted textareas that are NOT externally linked should be
// fully opaque and interactive.
const textareas = comfyPage.page.getByTestId(
TestIds.widgets.domWidgetTextarea
)
await expect(textareas.first()).toBeVisible()

const count = await textareas.count()
for (let i = 0; i < count; i++) {
const textarea = textareas.nth(i)
const wrapper = textarea.locator('..')
const opacity = await wrapper.evaluate(
(el) => getComputedStyle(el).opacity
)

if (opacity === '1') {
const testContent = `nested-promotion-edit-${i}`
await textarea.fill(testContent)
await expect(textarea).toHaveValue(testContent)
}
}
})
})

test.describe('Promotion Cleanup', () => {
test('Removing subgraph node clears promotion store entries', async ({
comfyPage
Expand Down
10 changes: 7 additions & 3 deletions src/components/graph/widgets/DomWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,17 @@ watch(
updateDomClipping()
}

const override = widgetState.positionOverride
const isDisabled = override
Comment thread
christian-byrne marked this conversation as resolved.
? (override.widget.computedDisabled ?? false)
: widget.computedDisabled

style.value = {
...positionStyle.value,
...(enableDomClipping.value ? clippingStyle.value : {}),
zIndex: widgetState.zIndex,
pointerEvents:
widgetState.readonly || widget.computedDisabled ? 'none' : 'auto',
opacity: widget.computedDisabled ? 0.5 : 1
pointerEvents: widgetState.readonly || isDisabled ? 'none' : 'auto',
opacity: isDisabled ? 0.5 : 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
},
{ deep: true }
Expand Down
131 changes: 106 additions & 25 deletions src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { reactive, shallowReactive } from 'vue'

import { useChainCallback } from '@/composables/functional/useChainCallback'
import { isPromotedWidgetView } from '@/core/graph/subgraph/promotedWidgetTypes'
import { resolveConcretePromotedWidget } from '@/core/graph/subgraph/resolveConcretePromotedWidget'
import { resolvePromotedWidgetSource } from '@/core/graph/subgraph/resolvePromotedWidgetSource'
import { resolveSubgraphInputLink } from '@/core/graph/subgraph/resolveSubgraphInputLink'
import type {
INodeInputSlot,
INodeOutputSlot
Expand Down Expand Up @@ -46,7 +48,9 @@ export interface WidgetSlotMetadata {
*/
export interface SafeWidgetData {
nodeId?: NodeId
storeNodeId?: NodeId
name: string
storeName?: string
type: string
/** Callback to invoke when widget value changes (wraps LiteGraph callback + triggerDraw) */
callback?: ((value: unknown) => void) | undefined
Expand Down Expand Up @@ -161,7 +165,7 @@ function getSharedWidgetEnhancements(
/**
* Validates that a value is a valid WidgetValue type
*/
const normalizeWidgetValue = (value: unknown): WidgetValue => {
function normalizeWidgetValue(value: unknown): WidgetValue {
if (value === null || value === undefined || value === void 0) {
return undefined
}
Expand Down Expand Up @@ -193,11 +197,68 @@ function safeWidgetMapper(
node: LGraphNode,
slotMetadata: Map<string, WidgetSlotMetadata>
): (widget: IBaseWidget) => SafeWidgetData {
function extractWidgetDisplayOptions(
widget: IBaseWidget
): SafeWidgetData['options'] {
if (!widget.options) return undefined

return {
canvasOnly: widget.options.canvasOnly,
advanced: widget.advanced,
hidden: widget.options.hidden,
read_only: widget.options.read_only
}
}

function resolveSourceSeedByInputName(inputName: string): {

Copy link
Copy Markdown
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
Copy Markdown
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.

sourceNodeId: string
sourceWidgetName: string
} | null {
const resolved = resolveSubgraphInputLink(
node,
inputName,
({ inputNode, targetInput, getTargetWidget }) => {
if (inputNode.isSubgraphNode()) {
return {
sourceNodeId: String(inputNode.id),
sourceWidgetName: targetInput.name
}
}

const targetWidget = getTargetWidget()
if (!targetWidget) return undefined
return {
sourceNodeId: String(inputNode.id),
sourceWidgetName: targetWidget.name
}
}
)

return resolved ?? null
}

return function (widget) {
try {
const promotedInputName = isPromotedWidgetView(widget)
? node.inputs?.find((input) => {
if (input.name === widget.name) return true
if (input._widget === widget) return true
return false
})?.name
: undefined
const displayName = promotedInputName ?? widget.name
const promotedSourceSeed =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): safeWidgetMapper now interleaves widget-to-view-model mapping, promoted widget resolution, and store identity computation. SafeWidgetData has grown to 4 identity fields (nodeId, storeNodeId, name, storeName).

Similar to the SubgraphNode comment - as the project moves toward ECS-style centralized state, consider extracting the promoted widget identity resolution into a pure helper (e.g. resolvePromotedWidgetIdentity(node, widget) -> { nodeId, storeNodeId, name, storeName, displayName }). The mapper would then just assemble final data from pre-resolved identities, keeping concerns separated.

Also a naming nit: promotedSourceSeed sounds like it relates to seed randomization (a first-class concept in ComfyUI). It's actually the starting point for resolving the concrete promoted widget. Same for resolveSourceSeedByInputName. Consider promotedSource / resolvePromotedSourceByInputName.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

One path names the source,
Mapper breathes in simpler lines,
Identity stays.

isPromotedWidgetView(widget) && displayName
? (resolveSourceSeedByInputName(displayName) ?? {
sourceNodeId: widget.sourceNodeId,
sourceWidgetName: widget.sourceWidgetName
})
: null

// Get shared enhancements (controlWidget, spec, nodeType)
const sharedEnhancements = getSharedWidgetEnhancements(node, widget)
const slotInfo = slotMetadata.get(widget.name)
const slotInfo =
slotMetadata.get(displayName) ?? slotMetadata.get(widget.name)

// Wrapper callback specific to Nodes 2.0 rendering
const callback = (v: unknown) => {
Expand All @@ -215,36 +276,52 @@ function safeWidgetMapper(
isPromotedWidgetView(widget) && widget.sourceWidgetName.startsWith('$$')

// Extract only render-critical options (canvasOnly, advanced, read_only)
const options = widget.options
? {
canvasOnly: widget.options.canvasOnly,
advanced: widget.advanced,
hidden: widget.options.hidden,
read_only: widget.options.read_only
}
: undefined
const options = extractWidgetDisplayOptions(widget)
const subgraphId = node.isSubgraphNode() && node.subgraph.id

const resolvedSourceResult =
isPromotedWidgetView(widget) && promotedSourceSeed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): the new nested resolution paths in safeWidgetMapper (storeNodeId, storeName, effectiveWidget type resolution) have no dedicated tests. A promoted widget whose concrete source is two layers deep should produce the correct storeNodeId, storeName, and type in SafeWidgetData.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create GitHub issue for adding tests for nested promoted widget resolution

gh issue create \
  --repo Comfy-Org/ComfyUI_frontend \
  --title "Add tests for nested promoted widget resolution paths in safeWidgetMapper" \
  --body "## Context

This issue tracks the need to add dedicated test coverage for the new nested resolution paths in \`safeWidgetMapper\` introduced in PR `#9282`.

## Test Coverage Needed

The following fields in \`SafeWidgetData\` need test coverage for promoted widgets whose concrete source is two layers deep:
- \`storeNodeId\`
- \`storeName\`
- \`effectiveWidget\` type resolution

## Scenario

A promoted widget whose concrete source is nested two layers deep should produce the correct \`storeNodeId\`, \`storeName\`, and \`type\` in \`SafeWidgetData\`.

## References

- Pull Request: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282
- Original Discussion: https://github.com/Comfy-Org/ComfyUI_frontend/pull/9282#discussion_r2866949168
- Requested by: `@christian-byrne`

## File

\`src/composables/graph/useGraphNodeManager.ts\`" \
  --assignee christian-byrne

Length of output: 57


@christian-byrne, I've created issue #9292 to track adding tests for the nested promoted widget resolution paths in safeWidgetMapper. The issue includes references to this PR and discussion, and has been assigned to you.


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

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

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

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

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

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

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

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

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

Learnt from: JemiloII
Repo: Comfy-Org/ComfyUI_frontend PR: 8282
File: src/composables/usePaste.test.ts:372-383
Timestamp: 2026-01-23T21:06:15.240Z
Learning: In paste/clipboard handling code, do not rely on ephemeral DataTransfer data across async boundaries. If an operation awaits async tasks (such as node creation) during a paste, clone the DataTransfer (e.g., using cloneDataTransfer) to persist files and string data before the paste event completes. This ensures clipboard contents remain available for subsequent processing.

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

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

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

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

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Through two nested graphs it would roam,
To find where the values call home.
Now tests verify clear,
storeNodeId is near,
And storeName and type match the dome.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

? resolveConcretePromotedWidget(
node,
promotedSourceSeed.sourceNodeId,
promotedSourceSeed.sourceWidgetName
)
: null
const resolvedSource =
resolvedSourceResult?.status === 'resolved'
? resolvedSourceResult.resolved
: undefined
const sourceWidget = resolvedSource?.widget
const sourceNode = resolvedSource?.node

const effectiveWidget = sourceWidget ?? widget

const localId = isPromotedWidgetView(widget)
? widget.sourceNodeId
? String(sourceNode?.id ?? promotedSourceSeed?.sourceNodeId)
: undefined
const nodeId =
subgraphId && localId ? `${subgraphId}:${localId}` : undefined
const name = isPromotedWidgetView(widget)
? widget.sourceWidgetName
: widget.name
const storeName = isPromotedWidgetView(widget)
? (sourceWidget?.name ?? promotedSourceSeed?.sourceWidgetName)
: undefined
const name = storeName ?? displayName

return {
nodeId,
storeNodeId: nodeId,
name,
type: widget.type,
storeName,
type: effectiveWidget.type,
...sharedEnhancements,
callback,
hasLayoutSize: typeof widget.computeLayoutSize === 'function',
hasLayoutSize: typeof effectiveWidget.computeLayoutSize === 'function',
isDOMWidget: isDOMWidget(widget) || isPromotedDOMWidget(widget),
options: isPromotedPseudoWidget
? { ...options, canvasOnly: true }
: options,
? {
...(extractWidgetDisplayOptions(effectiveWidget) ?? options),
canvasOnly: true
}
: (extractWidgetDisplayOptions(effectiveWidget) ?? options),
slotMetadata: slotInfo,
slotName: name !== widget.name ? widget.name : undefined
Comment thread
DrJKL marked this conversation as resolved.
}
Expand Down Expand Up @@ -312,14 +389,17 @@ export function extractVueNodeData(node: LGraphNode): VueNodeData {
})

const safeWidgets = reactiveComputed<SafeWidgetData[]>(() => {
const widgetsSnapshot = node.widgets ?? []

node.inputs?.forEach((input, index) => {
if (!input?.widget?.name) return
slotMetadata.set(input.widget.name, {
const slotInfo = {
index,
linked: input.link != null
})
}
if (input.name) slotMetadata.set(input.name, slotInfo)
if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo)
})
return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? []
return widgetsSnapshot.map(safeWidgetMapper(node, slotMetadata))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})

const nodeType =
Expand Down Expand Up @@ -375,11 +455,12 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
const slotMetadata = new Map<string, WidgetSlotMetadata>()

nodeRef.inputs?.forEach((input, index) => {
if (!input?.widget?.name) return
slotMetadata.set(input.widget.name, {
const slotInfo = {
index,
linked: input.link != null
})
}
if (input.name) slotMetadata.set(input.name, slotInfo)
if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo)
})

// Update only widgets with new slot metadata, keeping other widget data intact
Expand Down
8 changes: 7 additions & 1 deletion src/core/graph/subgraph/promotedWidgetTypes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode'
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'

export type ResolvedPromotedWidget = {
node: LGraphNode
widget: IBaseWidget
}

export interface PromotedWidgetView extends IBaseWidget {
readonly node: SubgraphNode
Expand Down
Loading
Loading