From 1749cfa6783109d86de5b6e08ba9a8ff462602e7 Mon Sep 17 00:00:00 2001
From: Arjan Singh <1598641+arjansingh@users.noreply.github.com>
Date: Wed, 24 Sep 2025 16:34:39 -0700
Subject: [PATCH] [fix] properly show error states (#5758)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
I want to take a more general look at `comfyApp.graph.onTrigger` but
this is the cleanest fix I could come up with for #5694.
I will explore simplifying onTrigger in a separate PR.
## Changes
1. Create a `node:slot-errors:changed` trigger.
2. Trigger it if we find any of the node slots have errors.
3. Check each node to see if there is any error present.
4. Add an error class if there are.
## Screenshots (if applicable)
Working error states!
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5758-fix-properly-show-error-states-2786d73d365081cbbf62c314c7f5f380)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Alexander Brown
---
src/components/graph/GraphCanvas.vue | 22 +++++++++++----
src/composables/graph/useGraphNodeManager.ts | 22 +++++++++++++++
.../vueNodes/components/LGraphNode.vue | 28 +++++++++++++++----
3 files changed, 60 insertions(+), 12 deletions(-)
diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue
index ac33aa280..5da16d0f0 100644
--- a/src/components/graph/GraphCanvas.vue
+++ b/src/components/graph/GraphCanvas.vue
@@ -307,13 +307,23 @@ watch(
removeSlotError(node)
const nodeErrors = lastNodeErrors?.[node.id]
if (!nodeErrors) continue
+
+ let slotErrorsChanged = false
for (const error of nodeErrors.errors) {
- if (error.extra_info && error.extra_info.input_name) {
- const inputIndex = node.findInputSlot(error.extra_info.input_name)
- if (inputIndex !== -1) {
- node.inputs[inputIndex].hasErrors = true
- }
- }
+ if (!error.extra_info?.input_name) continue
+
+ const inputIndex = node.findInputSlot(error.extra_info.input_name)
+ if (inputIndex === -1) continue
+
+ node.inputs[inputIndex].hasErrors = true
+ slotErrorsChanged = true
+ }
+
+ // Trigger Vue node data update if slot errors changed
+ if (slotErrorsChanged && comfyApp.graph.onTrigger) {
+ comfyApp.graph.onTrigger('node:slot-errors:changed', {
+ nodeId: node.id
+ })
}
}
diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts
index 31cc63a81..dbce2c84e 100644
--- a/src/composables/graph/useGraphNodeManager.ts
+++ b/src/composables/graph/useGraphNodeManager.ts
@@ -435,6 +435,28 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
})
}
}
+ } else if (
+ action === 'node:slot-errors:changed' &&
+ param &&
+ typeof param === 'object'
+ ) {
+ const event = param as { nodeId: string | number }
+ const nodeId = String(event.nodeId)
+ const litegraphNode = nodeRefs.get(nodeId)
+ const currentData = vueNodeData.get(nodeId)
+
+ if (litegraphNode && currentData) {
+ // Re-extract slot data with updated hasErrors properties
+ vueNodeData.set(nodeId, {
+ ...currentData,
+ inputs: litegraphNode.inputs
+ ? [...litegraphNode.inputs]
+ : undefined,
+ outputs: litegraphNode.outputs
+ ? [...litegraphNode.outputs]
+ : undefined
+ })
+ }
}
// Call original trigger handler if it exists
diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
index 08ff30d3b..2b3ec26ae 100644
--- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue
+++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
@@ -136,7 +136,11 @@ import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
-import { LiteGraph } from '@/lib/litegraph/src/litegraph'
+import {
+ type INodeInputSlot,
+ type INodeOutputSlot,
+ LiteGraph
+} from '@/lib/litegraph/src/litegraph'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
@@ -200,9 +204,21 @@ const hasExecutionError = computed(
)
// Computed error states for styling
-const hasAnyError = computed(
- (): boolean => !!(hasExecutionError.value || nodeData.hasErrors || error)
-)
+const hasAnyError = computed((): boolean => {
+ return (
+ !!hasExecutionError.value ||
+ !!nodeData.hasErrors ||
+ !!error ||
+ // Type assertions needed because VueNodeData.inputs/outputs are typed as unknown[]
+ // but at runtime they contain INodeInputSlot/INodeOutputSlot objects
+ !!nodeData.inputs?.some(
+ (slot) => (slot as INodeInputSlot)?.hasErrors ?? false
+ ) ||
+ !!nodeData.outputs?.some(
+ (slot) => (slot as INodeOutputSlot)?.hasErrors ?? false
+ )
+ )
+})
const bypassed = computed((): boolean => nodeData.mode === 4)
@@ -263,7 +279,7 @@ const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
const borderClass = computed(() => {
if (hasAnyError.value) {
- return 'border-error'
+ return 'border-red-500 dark-theme:border-red-500'
}
if (executing.value) {
return 'border-blue-500'
@@ -276,7 +292,7 @@ const outlineClass = computed(() => {
return undefined
}
if (hasAnyError.value) {
- return 'outline-error'
+ return 'outline-red-500'
}
if (executing.value) {
return 'outline-blue-500'