From 105c30a5143facd61c61988b6ea8eb7496d23952 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Mon, 11 Aug 2025 18:33:18 -0400 Subject: [PATCH 1/3] feat(vue-nodes): off-by-default unified feature flag; gate LiteGraph drawNode in Vue mode; mount DOM widgets only in legacy path; hide settings; add unbound commands; remove console helpers; fix typings/promises --- src/components/graph/GraphCanvas.vue | 77 ++++++++++++++++++++------- src/composables/useFeatureFlags.ts | 18 ++----- src/constants/coreSettings.ts | 26 ++++----- src/lib/litegraph/src/LGraphCanvas.ts | 13 +++++ src/schemas/apiSchema.ts | 1 + 5 files changed, 85 insertions(+), 50 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index b2c59bc266..3a1c951cb1 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -68,6 +68,7 @@ - + + @@ -194,7 +196,8 @@ const minimap = useMinimap() const { shouldRenderVueNodes, isDevModeEnabled } = useFeatureFlags() // TransformPane enabled when Vue nodes are enabled OR debug override -const debugOverrideVueNodes = ref(true) // Default to true for development +const debugOverrideVueNodes = ref(false) +const debugPanelVisible = ref(false) const transformPaneEnabled = computed( () => shouldRenderVueNodes.value || debugOverrideVueNodes.value ) @@ -274,6 +277,7 @@ watch(canvasRef, () => { // Vue node lifecycle management - initialize after graph is ready let nodeManager: ReturnType | null = null +let cleanupNodeManager: (() => void) | null = null const vueNodeData = ref>(new Map()) const nodeState = ref>(new Map()) const nodePositions = ref>( @@ -296,31 +300,49 @@ const performanceMetrics = reactive({ const nodeDataTrigger = ref(0) const initializeNodeManager = () => { - if (!comfyApp.graph || nodeManager) { - return - } - + if (!comfyApp.graph || nodeManager) return nodeManager = useGraphNodeManager(comfyApp.graph) - + cleanupNodeManager = nodeManager.cleanup // Use the manager's reactive maps directly vueNodeData.value = nodeManager.vueNodeData nodeState.value = nodeManager.nodeState nodePositions.value = nodeManager.nodePositions nodeSizes.value = nodeManager.nodeSizes - detectChangesInRAF = nodeManager.detectChangesInRAF Object.assign(performanceMetrics, nodeManager.performanceMetrics) - // Force computed properties to re-evaluate nodeDataTrigger.value++ } -// Watch for graph availability +const disposeNodeManager = () => { + if (!nodeManager) return + try { + cleanupNodeManager?.() + } catch { + /* empty */ + } + nodeManager = null + cleanupNodeManager = null + // Reset reactive maps to inert defaults + vueNodeData.value = new Map() + nodeState.value = new Map() + nodePositions.value = new Map() + nodeSizes.value = new Map() + // Reset metrics + performanceMetrics.frameTime = 0 + performanceMetrics.updateTime = 0 + performanceMetrics.nodeCount = 0 + performanceMetrics.culledCount = 0 +} + +// Watch for transformPaneEnabled to gate the node manager lifecycle watch( - () => comfyApp.graph, - (graph) => { - if (graph) { + () => transformPaneEnabled.value && Boolean(comfyApp.graph), + (enabled) => { + if (enabled) { initializeNodeManager() + } else { + disposeNodeManager() } }, { immediate: true } @@ -700,11 +722,6 @@ onMounted(async () => { comfyAppReady.value = true - // Initialize node manager after setup is complete - if (comfyApp.graph) { - initializeNodeManager() - } - comfyApp.canvas.onSelectionChange = useChainCallback( comfyApp.canvas.onSelectionChange, () => canvasStore.updateSelectedItems() @@ -719,6 +736,30 @@ onMounted(async () => { await workflowPersistence.restorePreviousWorkflow() workflowPersistence.restoreWorkflowTabsState() + // Register experimental commands (unbound by default) + const commandStore = useCommandStore() + if (!commandStore.isRegistered('Experimental.ToggleVueNodes')) { + commandStore.registerCommand({ + id: 'Experimental.ToggleVueNodes', + label: () => + `Experimental: ${shouldRenderVueNodes.value ? 'Disable' : 'Enable'} Vue Nodes`, + function: async () => { + const current = settingStore.get('Comfy.VueNodes.Enabled') ?? false + await settingStore.set('Comfy.VueNodes.Enabled', !current) + } + }) + } + if (!commandStore.isRegistered('Experimental.ToggleVueNodeDebugPanel')) { + commandStore.registerCommand({ + id: 'Experimental.ToggleVueNodeDebugPanel', + label: () => + `Experimental: ${debugPanelVisible.value ? 'Hide' : 'Show'} Vue Node Debug Panel`, + function: () => { + debugPanelVisible.value = !debugPanelVisible.value + } + }) + } + // Initialize release store to fetch releases from comfy-api (fire-and-forget) const { useReleaseStore } = await import('@/stores/releaseStore') const releaseStore = useReleaseStore() diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index e503ac9891..50fc61a6ec 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -17,21 +17,10 @@ export const useFeatureFlags = () => { */ const isVueNodesEnabled = computed(() => { try { - return settingStore.get('Comfy.VueNodes.Enabled' as any) ?? true // Default to true for development + // Off by default: ensure Vue nodes are disabled unless explicitly enabled + return settingStore.get('Comfy.VueNodes.Enabled') ?? false } catch { - return true // Default to true for development - } - }) - - /** - * Enable Vue widget rendering within Vue nodes - * When disabled, Vue nodes render without widgets (structure only) - */ - const isVueWidgetsEnabled = computed(() => { - try { - return settingStore.get('Comfy.VueNodes.Widgets' as any) ?? true - } catch { - return true + return false } }) @@ -74,7 +63,6 @@ export const useFeatureFlags = () => { return { isVueNodesEnabled, - isVueWidgetsEnabled, isDevModeEnabled, shouldRenderVueNodes, syncVueNodesFlag diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts index b83a7a7d0a..8c57b04e39 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -893,24 +893,16 @@ export const CORE_SETTINGS: SettingParams[] = [ defaultValue: 0 }, - // Vue Node System Settings + /** + * Vue Node System Settings + */ { - id: 'Comfy.VueNodes.Enabled' as any, - category: ['Comfy', 'Vue Nodes'], - experimental: true, - name: 'Enable Vue node rendering', + id: 'Comfy.VueNodes.Enabled', + name: 'Enable Vue node rendering (hidden)', + type: 'hidden', tooltip: - 'Render nodes as Vue components instead of canvas elements. Experimental feature.', - type: 'boolean', - defaultValue: false - }, - { - id: 'Comfy.VueNodes.Widgets' as any, - category: ['Comfy', 'Vue Nodes', 'Widgets'], - experimental: true, - name: 'Enable Vue widgets', - tooltip: 'Render widgets as Vue components within Vue nodes.', - type: 'boolean', - defaultValue: true + 'Render nodes as Vue components instead of canvas. Hidden; toggle via Experimental keybinding.', + defaultValue: false, + experimental: true } ] diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index cbb644cbff..8016ec0eb7 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -4947,6 +4947,19 @@ export class LGraphCanvas drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void { this.current_node = node + // When Vue nodes mode is enabled, LiteGraph should not draw node chrome or widgets. + // We still need to keep slot metrics and layout in sync for hit-testing and links. + // Interaction system changes coming later, chances are vue nodes mode will be mostly broken on land + if (LiteGraph.vueNodesMode) { + // Prepare concrete slots and compute layout measures without rendering visuals. + node._setConcreteSlots() + if (!node.collapsed) { + node.arrange() + } + // Skip all node body/widget/title rendering. Vue overlay handles visuals. + return + } + const color = node.renderingColor const bgcolor = node.renderingBgColor diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index 00e51c7a23..c9eeb6ac7a 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -477,6 +477,7 @@ const zSettings = z.object({ 'Comfy.Node.AllowImageSizeDraw': z.boolean(), 'Comfy.Minimap.Visible': z.boolean(), 'Comfy.Canvas.NavigationMode': z.string(), + 'Comfy.VueNodes.Enabled': z.boolean(), 'Comfy-Desktop.AutoUpdate': z.boolean(), 'Comfy-Desktop.SendStatistics': z.boolean(), 'Comfy-Desktop.WindowStyle': z.string(), From a37281eee0dd9bd47a50aea150d861ed3a9e43d0 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Mon, 11 Aug 2025 19:17:18 -0400 Subject: [PATCH 2/3] chore(commands): move experimental Vue Nodes commands to core; add hidden setting for debug panel visibility; persist debug panel state; remove per-canvas registrations --- src/components/graph/GraphCanvas.vue | 32 ++++++---------------------- src/composables/useCoreCommands.ts | 27 +++++++++++++++++++++++ src/constants/coreSettings.ts | 7 ++++++ src/schemas/apiSchema.ts | 1 + 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 3a1c951cb1..f4579cce0b 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -197,7 +197,13 @@ const { shouldRenderVueNodes, isDevModeEnabled } = useFeatureFlags() // TransformPane enabled when Vue nodes are enabled OR debug override const debugOverrideVueNodes = ref(false) -const debugPanelVisible = ref(false) +// Persist debug panel visibility in settings so core commands can toggle it +const debugPanelVisible = computed({ + get: () => settingStore.get('Comfy.VueNodes.DebugPanel.Visible') ?? false, + set: (v: boolean) => { + void settingStore.set('Comfy.VueNodes.DebugPanel.Visible', v) + } +}) const transformPaneEnabled = computed( () => shouldRenderVueNodes.value || debugOverrideVueNodes.value ) @@ -736,30 +742,6 @@ onMounted(async () => { await workflowPersistence.restorePreviousWorkflow() workflowPersistence.restoreWorkflowTabsState() - // Register experimental commands (unbound by default) - const commandStore = useCommandStore() - if (!commandStore.isRegistered('Experimental.ToggleVueNodes')) { - commandStore.registerCommand({ - id: 'Experimental.ToggleVueNodes', - label: () => - `Experimental: ${shouldRenderVueNodes.value ? 'Disable' : 'Enable'} Vue Nodes`, - function: async () => { - const current = settingStore.get('Comfy.VueNodes.Enabled') ?? false - await settingStore.set('Comfy.VueNodes.Enabled', !current) - } - }) - } - if (!commandStore.isRegistered('Experimental.ToggleVueNodeDebugPanel')) { - commandStore.registerCommand({ - id: 'Experimental.ToggleVueNodeDebugPanel', - label: () => - `Experimental: ${debugPanelVisible.value ? 'Hide' : 'Show'} Vue Node Debug Panel`, - function: () => { - debugPanelVisible.value = !debugPanelVisible.value - } - }) - } - // Initialize release store to fetch releases from comfy-api (fire-and-forget) const { useReleaseStore } = await import('@/stores/releaseStore') const releaseStore = useReleaseStore() diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 9752321f93..fa03250495 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -256,6 +256,33 @@ export function useCoreCommands(): ComfyCommand[] { app.canvas.setDirty(true, true) } }, + { + id: 'Experimental.ToggleVueNodes', + label: () => + `Experimental: ${ + useSettingStore().get('Comfy.VueNodes.Enabled') ? 'Disable' : 'Enable' + } Vue Nodes`, + function: async () => { + const settingStore = useSettingStore() + const current = settingStore.get('Comfy.VueNodes.Enabled') ?? false + await settingStore.set('Comfy.VueNodes.Enabled', !current) + } + }, + { + id: 'Experimental.ToggleVueNodeDebugPanel', + label: () => + `Experimental: ${ + useSettingStore().get('Comfy.VueNodes.DebugPanel.Visible') + ? 'Hide' + : 'Show' + } Vue Node Debug Panel`, + function: async () => { + const settingStore = useSettingStore() + const current = + settingStore.get('Comfy.VueNodes.DebugPanel.Visible') ?? false + await settingStore.set('Comfy.VueNodes.DebugPanel.Visible', !current) + } + }, { id: 'Comfy.Canvas.FitView', icon: 'pi pi-expand', diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts index 8c57b04e39..5e1a38389f 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -904,5 +904,12 @@ export const CORE_SETTINGS: SettingParams[] = [ 'Render nodes as Vue components instead of canvas. Hidden; toggle via Experimental keybinding.', defaultValue: false, experimental: true + }, + { + id: 'Comfy.VueNodes.DebugPanel.Visible', + name: 'Vue Nodes Debug Panel Visible (hidden)', + type: 'hidden', + defaultValue: false, + experimental: true } ] diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index c9eeb6ac7a..f8aa76b59b 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -478,6 +478,7 @@ const zSettings = z.object({ 'Comfy.Minimap.Visible': z.boolean(), 'Comfy.Canvas.NavigationMode': z.string(), 'Comfy.VueNodes.Enabled': z.boolean(), + 'Comfy.VueNodes.DebugPanel.Visible': z.boolean(), 'Comfy-Desktop.AutoUpdate': z.boolean(), 'Comfy-Desktop.SendStatistics': z.boolean(), 'Comfy-Desktop.WindowStyle': z.string(), From c090aebdc56679561959cd710cc2914ed5e28c11 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Mon, 11 Aug 2025 20:01:22 -0400 Subject: [PATCH 3/3] chore(widgets): remove redundant vueNodesMode early return in DomWidgets since component is not mounted in Vue mode --- src/components/graph/DomWidgets.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/graph/DomWidgets.vue b/src/components/graph/DomWidgets.vue index 25136b887c..7edca3f5e2 100644 --- a/src/components/graph/DomWidgets.vue +++ b/src/components/graph/DomWidgets.vue @@ -16,7 +16,6 @@ import { computed } from 'vue' import DomWidget from '@/components/graph/widgets/DomWidget.vue' import { useChainCallback } from '@/composables/functional/useChainCallback' -import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { useDomWidgetStore } from '@/stores/domWidgetStore' import { useCanvasStore } from '@/stores/graphStore' @@ -28,9 +27,6 @@ const updateWidgets = () => { const lgCanvas = canvasStore.canvas if (!lgCanvas) return - // Skip updating DOM widgets when Vue nodes mode is enabled - if (LiteGraph.vueNodesMode) return - const lowQuality = lgCanvas.low_quality const currentGraph = lgCanvas.graph