From 39e3cb7e91caabb198d1c6dc3ef13325cb271f45 Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Tue, 27 Jan 2026 20:43:58 -0800 Subject: [PATCH 1/3] feat: support dev-only nodes Support `dev_only` property to node definitions that hides nodes from search and menus unless dev mode is enabled. Dev-only nodes display a "DEV" badge when visible. This functionality is primarily intended to support unit-testing nodes on Comfy Cloud, but also has other uses. --- src/components/graph/GraphCanvas.vue | 4 ++++ src/components/searchbox/NodeSearchItem.vue | 11 ++++++----- src/locales/en/main.json | 1 + src/schemas/nodeDef/nodeDefSchemaV2.ts | 1 + src/schemas/nodeDefSchema.ts | 1 + src/stores/nodeDefStore.ts | 13 +++++++++++++ 6 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 08e7573a9b1..62fba2c78ca 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -260,6 +260,10 @@ watchEffect(() => { ) }) +watchEffect(() => { + nodeDefStore.showDevOnly = settingStore.get('Comfy.DevMode') +}) + watchEffect(() => { const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck') const textareas = document.querySelectorAll( diff --git a/src/components/searchbox/NodeSearchItem.vue b/src/components/searchbox/NodeSearchItem.vue index da9a0cd830a..a9859eca4d5 100644 --- a/src/components/searchbox/NodeSearchItem.vue +++ b/src/components/searchbox/NodeSearchItem.vue @@ -21,16 +21,17 @@
- + + { const nodeDefsByDisplayName = ref>({}) const showDeprecated = ref(false) const showExperimental = ref(false) + const showDevOnly = ref(false) const nodeDefFilters = ref([]) const nodeDefs = computed(() => { @@ -422,6 +426,14 @@ export const useNodeDefStore = defineStore('nodeDef', () => { predicate: (nodeDef) => showExperimental.value || !nodeDef.experimental }) + // Dev-only nodes filter + registerNodeDefFilter({ + id: 'core.dev_only', + name: 'Hide Dev-Only Nodes', + description: 'Hides nodes marked as dev-only unless dev mode is enabled', + predicate: (nodeDef) => showDevOnly.value || !nodeDef.dev_only + }) + // Subgraph nodes filter // Filter out litegraph typed subgraphs, saved blueprints are added in separately registerNodeDefFilter({ @@ -446,6 +458,7 @@ export const useNodeDefStore = defineStore('nodeDef', () => { nodeDefsByDisplayName, showDeprecated, showExperimental, + showDevOnly, nodeDefFilters, nodeDefs, From 1645b1e967c16fa9fce3f8eac15be7cd295754d1 Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Tue, 27 Jan 2026 21:22:50 -0800 Subject: [PATCH 2/3] Remove dev nodes from context menu --- src/components/graph/GraphCanvas.vue | 12 +++++++++++- src/lib/litegraph/src/LGraphNode.ts | 6 ++++++ src/services/litegraphService.ts | 11 +++++++++-- src/utils/nodeFilterUtil.test.ts | 4 ++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 62fba2c78ca..79fc1b28cd6 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -261,7 +261,17 @@ watchEffect(() => { }) watchEffect(() => { - nodeDefStore.showDevOnly = settingStore.get('Comfy.DevMode') + const devModeEnabled = settingStore.get('Comfy.DevMode') + nodeDefStore.showDevOnly = devModeEnabled + + // Update skip_list on all registered node types for dev-only nodes + // This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory + // correctly filter dev-only nodes from the right-click context menu + for (const nodeType of Object.values(LiteGraph.registered_node_types)) { + if (nodeType.nodeData?.dev_only) { + nodeType.skip_list = !devModeEnabled + } + } }) watchEffect(() => { diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index d4d50537c0f..9876b69ac22 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -233,6 +233,12 @@ export class LGraphNode static description?: string static filter?: string static skip_list?: boolean + static nodeData?: { + dev_only?: boolean + deprecated?: boolean + experimental?: boolean + [key: string]: unknown + } static resizeHandleSize = 15 static resizeEdgeSize = 5 diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 18d281973d7..f071a235642 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -261,7 +261,7 @@ export const useLitegraphService = () => { static comfyClass: string static override title: string static override category: string - static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 + static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 _initialMinSize = { width: 1, height: 1 } @@ -394,7 +394,7 @@ export const useLitegraphService = () => { static comfyClass: string static override title: string static override category: string - static nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 + static override nodeData: ComfyNodeDefV1 & ComfyNodeDefV2 _initialMinSize = { width: 1, height: 1 } @@ -496,6 +496,13 @@ export const useLitegraphService = () => { // because `registerNodeType` will overwrite the assignments. node.category = nodeDef.category node.title = nodeDef.display_name || nodeDef.name + + // Set skip_list for dev-only nodes based on current DevMode setting + // This ensures nodes registered after initial load respect the current setting + if (nodeDef.dev_only) { + const settingStore = useSettingStore() + node.skip_list = !settingStore.get('Comfy.DevMode') + } } /** diff --git a/src/utils/nodeFilterUtil.test.ts b/src/utils/nodeFilterUtil.test.ts index 871f66109d7..fd64eab7c52 100644 --- a/src/utils/nodeFilterUtil.test.ts +++ b/src/utils/nodeFilterUtil.test.ts @@ -11,7 +11,7 @@ describe('nodeFilterUtil', () => { ): LGraphNode => { // Create a custom class with the nodeData static property class MockNode extends LGraphNode { - static nodeData = isOutputNode ? { output_node: true } : {} + static override nodeData = isOutputNode ? { output_node: true } : {} } const node = new MockNode('') @@ -72,7 +72,7 @@ describe('nodeFilterUtil', () => { it('should handle nodes with undefined output_node', () => { class MockNodeWithOtherData extends LGraphNode { - static nodeData = { someOtherProperty: true } + static override nodeData = { someOtherProperty: true } } const node = new MockNodeWithOtherData('') From 65c49c71a373027863d019f852daf234511521d7 Mon Sep 17 00:00:00 2001 From: Jacob Segal Date: Wed, 28 Jan 2026 18:37:52 -0800 Subject: [PATCH 3/3] Address PR review comments --- src/components/graph/GraphCanvas.vue | 14 -------------- src/lib/litegraph/src/LGraphNode.ts | 4 +++- src/stores/nodeDefStore.ts | 20 ++++++++++++++++++-- src/utils/nodeFilterUtil.test.ts | 6 +++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 79fc1b28cd6..08e7573a9b1 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -260,20 +260,6 @@ watchEffect(() => { ) }) -watchEffect(() => { - const devModeEnabled = settingStore.get('Comfy.DevMode') - nodeDefStore.showDevOnly = devModeEnabled - - // Update skip_list on all registered node types for dev-only nodes - // This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory - // correctly filter dev-only nodes from the right-click context menu - for (const nodeType of Object.values(LiteGraph.registered_node_types)) { - if (nodeType.nodeData?.dev_only) { - nodeType.skip_list = !devModeEnabled - } - } -}) - watchEffect(() => { const spellcheckEnabled = settingStore.get('Comfy.TextareaWidget.Spellcheck') const textareas = document.querySelectorAll( diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 9876b69ac22..a9dc95ccbf9 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -237,7 +237,9 @@ export class LGraphNode dev_only?: boolean deprecated?: boolean experimental?: boolean - [key: string]: unknown + output_node?: boolean + api_node?: boolean + name?: string } static resizeHandleSize = 15 diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index ef626fdd905..7bfa17c2c0e 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -1,9 +1,10 @@ import axios from 'axios' import _ from 'es-toolkit/compat' import { defineStore } from 'pinia' -import { computed, ref } from 'vue' +import { computed, ref, watchEffect } from 'vue' import { isProxyWidget } from '@/core/graph/subgraph/proxyWidget' +import { LiteGraph } from '@/lib/litegraph/src/litegraph' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { transformNodeDefV1ToV2 } from '@/schemas/nodeDef/migration' import type { @@ -17,6 +18,7 @@ import type { ComfyOutputTypesSpec as ComfyOutputSpecV1, PriceBadge } from '@/schemas/nodeDefSchema' +import { useSettingStore } from '@/platform/settings/settingStore' import { NodeSearchService } from '@/services/nodeSearchService' import { useSubgraphStore } from '@/stores/subgraphStore' import { NodeSourceType, getNodeSource } from '@/types/nodeSource' @@ -302,13 +304,27 @@ export interface NodeDefFilter { } export const useNodeDefStore = defineStore('nodeDef', () => { + const settingStore = useSettingStore() + const nodeDefsByName = ref>({}) const nodeDefsByDisplayName = ref>({}) const showDeprecated = ref(false) const showExperimental = ref(false) - const showDevOnly = ref(false) + const showDevOnly = computed(() => settingStore.get('Comfy.DevMode')) const nodeDefFilters = ref([]) + // Update skip_list on all registered node types when dev mode changes + // This ensures LiteGraph's getNodeTypesCategories/getNodeTypesInCategory + // correctly filter dev-only nodes from the right-click context menu + watchEffect(() => { + const devModeEnabled = showDevOnly.value + for (const nodeType of Object.values(LiteGraph.registered_node_types)) { + if (nodeType.nodeData?.dev_only) { + nodeType.skip_list = !devModeEnabled + } + } + }) + const nodeDefs = computed(() => { const subgraphStore = useSubgraphStore() // Blueprints first for discoverability in the node library sidebar diff --git a/src/utils/nodeFilterUtil.test.ts b/src/utils/nodeFilterUtil.test.ts index fd64eab7c52..0e6149c0d12 100644 --- a/src/utils/nodeFilterUtil.test.ts +++ b/src/utils/nodeFilterUtil.test.ts @@ -71,11 +71,11 @@ describe('nodeFilterUtil', () => { }) it('should handle nodes with undefined output_node', () => { - class MockNodeWithOtherData extends LGraphNode { - static override nodeData = { someOtherProperty: true } + class MockNodeWithEmptyData extends LGraphNode { + static override nodeData = {} } - const node = new MockNodeWithOtherData('') + const node = new MockNodeWithEmptyData('') node.id = 1 const result = filterOutputNodes([node])