From 6f77d274a486720d78e3afa84f4e95c028f07341 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Wed, 26 Nov 2025 16:50:32 -0800 Subject: [PATCH] [backport core/1.33] fix: don't use registry when only checking for presence of missing nodes (#6974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Backport of #6965 onto core/1.33 (clean cherry-pick of 83f04490b). ## Testing - pnpm typecheck ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6974-backport-core-1-33-fix-don-t-use-registry-when-only-checking-for-presence-of-missing-n-2b86d73d3650813dac37e224f857f296) by [Unito](https://www.unito.io) --- .../ComfyRunButton/ComfyQueueButton.vue | 9 +- .../breadcrumb/SubgraphBreadcrumbItem.vue | 9 +- .../manager/utils/graphHasMissingNodes.ts | 37 ++++++ .../utils/graphHasMissingNodes.test.ts | 115 ++++++++++++++++++ 4 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/workbench/extensions/manager/utils/graphHasMissingNodes.ts create mode 100644 tests-ui/tests/workbench/extensions/manager/utils/graphHasMissingNodes.test.ts diff --git a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue index db28d980c..c9e849554 100644 --- a/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue +++ b/src/components/actionbar/ComfyRunButton/ComfyQueueButton.vue @@ -44,17 +44,22 @@ import { useI18n } from 'vue-i18n' import { isCloud } from '@/platform/distribution/types' import { useTelemetry } from '@/platform/telemetry' +import { app } from '@/scripts/app' import { useCommandStore } from '@/stores/commandStore' +import { useNodeDefStore } from '@/stores/nodeDefStore' import { useQueueSettingsStore } from '@/stores/queueStore' import { useWorkspaceStore } from '@/stores/workspaceStore' -import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes' +import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes' import BatchCountEdit from '../BatchCountEdit.vue' const workspaceStore = useWorkspaceStore() const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore()) -const { hasMissingNodes } = useMissingNodes() +const nodeDefStore = useNodeDefStore() +const hasMissingNodes = computed(() => + graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName) +) const { t } = useI18n() const queueModeMenuItemLookup = computed(() => { diff --git a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue index 2e8692558..2181cce1b 100644 --- a/src/components/breadcrumb/SubgraphBreadcrumbItem.vue +++ b/src/components/breadcrumb/SubgraphBreadcrumbItem.vue @@ -64,11 +64,13 @@ import { ComfyWorkflow, useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' +import { app } from '@/scripts/app' import { useDialogService } from '@/services/dialogService' import { useCommandStore } from '@/stores/commandStore' +import { useNodeDefStore } from '@/stores/nodeDefStore' import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore' import { appendJsonExt } from '@/utils/formatUtil' -import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes' +import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes' interface Props { item: MenuItem @@ -79,7 +81,10 @@ const props = withDefaults(defineProps(), { isActive: false }) -const { hasMissingNodes } = useMissingNodes() +const nodeDefStore = useNodeDefStore() +const hasMissingNodes = computed(() => + graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName) +) const { t } = useI18n() const menu = ref & MenuState>() diff --git a/src/workbench/extensions/manager/utils/graphHasMissingNodes.ts b/src/workbench/extensions/manager/utils/graphHasMissingNodes.ts new file mode 100644 index 000000000..ddf3180ab --- /dev/null +++ b/src/workbench/extensions/manager/utils/graphHasMissingNodes.ts @@ -0,0 +1,37 @@ +import { unref } from 'vue' +import type { MaybeRef } from 'vue' + +import type { + LGraph, + LGraphNode, + Subgraph +} from '@/lib/litegraph/src/litegraph' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' +import { collectAllNodes } from '@/utils/graphTraversalUtil' + +export type NodeDefLookup = Record + +const isNodeMissingDefinition = ( + node: LGraphNode, + nodeDefsByName: NodeDefLookup +) => { + const nodeName = node?.type + if (!nodeName) return false + return !nodeDefsByName[nodeName] +} + +export const collectMissingNodes = ( + graph: LGraph | Subgraph | null | undefined, + nodeDefsByName: MaybeRef +): LGraphNode[] => { + if (!graph) return [] + const lookup = unref(nodeDefsByName) + return collectAllNodes(graph, (node) => isNodeMissingDefinition(node, lookup)) +} + +export const graphHasMissingNodes = ( + graph: LGraph | Subgraph | null | undefined, + nodeDefsByName: MaybeRef +) => { + return collectMissingNodes(graph, nodeDefsByName).length > 0 +} diff --git a/tests-ui/tests/workbench/extensions/manager/utils/graphHasMissingNodes.test.ts b/tests-ui/tests/workbench/extensions/manager/utils/graphHasMissingNodes.test.ts new file mode 100644 index 000000000..2badaa7cc --- /dev/null +++ b/tests-ui/tests/workbench/extensions/manager/utils/graphHasMissingNodes.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, it } from 'vitest' + +import type { + LGraph, + LGraphNode, + Subgraph +} from '@/lib/litegraph/src/litegraph' +import { + collectMissingNodes, + graphHasMissingNodes +} from '@/workbench/extensions/manager/utils/graphHasMissingNodes' +import type { NodeDefLookup } from '@/workbench/extensions/manager/utils/graphHasMissingNodes' +import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore' + +type NodeDefs = NodeDefLookup + +let nodeIdCounter = 0 +const mockNodeDef = {} as ComfyNodeDefImpl + +const createGraph = (nodes: LGraphNode[] = []): LGraph => { + return { nodes } as Partial as LGraph +} + +const createSubgraph = (nodes: LGraphNode[]): Subgraph => { + return { nodes } as Partial as Subgraph +} + +const createNode = ( + type?: string, + subgraphNodes?: LGraphNode[] +): LGraphNode => { + return { + id: nodeIdCounter++, + type, + isSubgraphNode: subgraphNodes ? () => true : undefined, + subgraph: subgraphNodes ? createSubgraph(subgraphNodes) : undefined + } as unknown as LGraphNode +} + +describe('graphHasMissingNodes', () => { + it('returns false when graph is null', () => { + expect(graphHasMissingNodes(null, {})).toBe(false) + }) + + it('returns false when graph is undefined', () => { + expect(graphHasMissingNodes(undefined, {})).toBe(false) + }) + + it('returns false when graph has no nodes', () => { + expect(graphHasMissingNodes(createGraph(), {})).toBe(false) + }) + + it('returns false when every node has a definition', () => { + const graph = createGraph([createNode('FooNode'), createNode('BarNode')]) + const nodeDefs: NodeDefs = { + FooNode: mockNodeDef, + BarNode: mockNodeDef + } + + expect(graphHasMissingNodes(graph, nodeDefs)).toBe(false) + }) + + it('returns true when at least one node is missing', () => { + const graph = createGraph([ + createNode('FooNode'), + createNode('MissingNode') + ]) + const nodeDefs: NodeDefs = { + FooNode: mockNodeDef + } + + expect(graphHasMissingNodes(graph, nodeDefs)).toBe(true) + }) + + it('checks nodes nested in subgraphs', () => { + const graph = createGraph([ + createNode('ContainerNode', [createNode('InnerMissing')]) + ]) + const nodeDefs: NodeDefs = { + ContainerNode: mockNodeDef + } + + const missingNodes = collectMissingNodes(graph, nodeDefs) + expect(missingNodes).toHaveLength(1) + expect(missingNodes[0]?.type).toBe('InnerMissing') + }) + + it('ignores nodes without a type', () => { + const graph = createGraph([ + createNode(undefined), + createNode(null as unknown as string) + ]) + + expect(graphHasMissingNodes(graph, {})).toBe(false) + }) + + it('traverses deeply nested subgraphs', () => { + const deepGraph = createGraph([ + createNode('Layer1', [ + createNode('Layer2', [ + createNode('Layer3', [createNode('MissingDeep')]) + ]) + ]) + ]) + const nodeDefs: NodeDefs = { + Layer1: mockNodeDef, + Layer2: mockNodeDef, + Layer3: mockNodeDef + } + + const missingNodes = collectMissingNodes(deepGraph, nodeDefs) + expect(missingNodes).toHaveLength(1) + expect(missingNodes[0]?.type).toBe('MissingDeep') + }) +})