From 4098b8961213d6d266e8b1fd22ad63af3a0e1a39 Mon Sep 17 00:00:00 2001 From: bymyself Date: Sat, 8 Nov 2025 09:20:41 -0800 Subject: [PATCH 1/6] show slot errors in subgraph --- src/scripts/app.ts | 47 ++++++ src/services/litegraphService.ts | 6 +- src/stores/executionStore.ts | 55 ++++++- tests-ui/tests/store/executionStore.test.ts | 170 ++++++++++++++++++++ 4 files changed, 275 insertions(+), 3 deletions(-) diff --git a/src/scripts/app.ts b/src/scripts/app.ts index a0479b5b28..a716937e27 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1286,6 +1286,49 @@ export class ComfyApp { }) } + /** + * Updates slot.hasErrors flags based on current validation errors. + * Clears all slot errors in the graph hierarchy, then sets hasErrors=true + * for slots that have validation errors in the execution store. + * Call this when lastNodeErrors changes (before queue, after response, in clean()). + */ + private updateSlotErrorFlags(): void { + if (!this.graph) return + + const executionStore = useExecutionStore() + + // Clear all slot errors in entire hierarchy + forEachNode(this.graph, (node) => { + if (node.inputs) { + for (const slot of node.inputs) { + slot.hasErrors = false + } + } + }) + + // Set slot errors from execution store + if (!executionStore.lastNodeErrors) return + + for (const [executionId, nodeError] of Object.entries( + executionStore.lastNodeErrors + )) { + // Find node by execution ID (handles subgraphs) + const node = getNodeByExecutionId(this.graph, executionId) + if (!node || !node.inputs) continue + + // Set slot errors for this node + for (const error of nodeError.errors) { + const slotName = error.extra_info?.input_name + if (!slotName) continue + + const slot = node.inputs.find((s) => s.name === slotName) + if (slot) { + slot.hasErrors = true + } + } + } + } + async queuePrompt( number: number, batchCount: number = 1, @@ -1301,6 +1344,7 @@ export class ComfyApp { this.processingQueue = true const executionStore = useExecutionStore() executionStore.lastNodeErrors = null + this.updateSlotErrorFlags() let comfyOrgAuthToken = await useFirebaseAuthStore().getIdToken() let comfyOrgApiKey = useApiKeyAuthStore().getApiKey() @@ -1327,6 +1371,7 @@ export class ComfyApp { delete api.authToken delete api.apiKey executionStore.lastNodeErrors = res.node_errors ?? null + this.updateSlotErrorFlags() if (executionStore.lastNodeErrors?.length) { this.canvas.draw(true, true) } else { @@ -1350,6 +1395,7 @@ export class ComfyApp { if (error instanceof PromptExecutionError) { executionStore.lastNodeErrors = error.response.node_errors ?? null + this.updateSlotErrorFlags() this.canvas.draw(true, true) } break @@ -1650,6 +1696,7 @@ export class ComfyApp { const executionStore = useExecutionStore() executionStore.lastNodeErrors = null executionStore.lastExecutionError = null + this.updateSlotErrorFlags() useDomWidgetStore().clear() diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 3fe45fcea2..c4b45097cf 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -151,7 +151,8 @@ export const useLitegraphService = () => { } } this.strokeStyles['nodeError'] = function (this: LGraphNode) { - if (app.lastNodeErrors?.[this.id]?.errors) { + const nodeLocatorId = useWorkflowStore().nodeToNodeLocatorId(this) + if (useExecutionStore().getNodeErrors(nodeLocatorId)) { return { color: 'red' } } } @@ -416,7 +417,8 @@ export const useLitegraphService = () => { } } this.strokeStyles['nodeError'] = function (this: LGraphNode) { - if (app.lastNodeErrors?.[this.id]?.errors) { + const nodeLocatorId = useWorkflowStore().nodeToNodeLocatorId(this) + if (useExecutionStore().getNodeErrors(nodeLocatorId)) { return { color: 'red' } } } diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index aba407abdf..c0fbf5edf1 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -243,6 +243,56 @@ export const useExecutionStore = defineStore('execution', () => { return localId != null ? String(localId) : null }) + /** + * Map of locator IDs to node errors (computed once when errors change). + * Converts execution IDs from backend to locator IDs for O(1) lookup. + */ + const nodeErrorsByLocatorId = computed>( + () => { + if (!lastNodeErrors.value) return {} + + const map: Record = {} + + for (const [executionId, nodeError] of Object.entries( + lastNodeErrors.value + )) { + // Convert execution ID to locator ID for subgraph support + const locatorId = executionIdToNodeLocatorId(executionId) + if (locatorId) { + map[locatorId] = nodeError + } + } + + return map + } + ) + + /** + * O(1) lookup for node errors by locator ID. + * Works for both root graph and subgraph nodes. + */ + const getNodeErrors = ( + nodeLocatorId: NodeLocatorId + ): NodeError | undefined => { + return nodeErrorsByLocatorId.value[nodeLocatorId] + } + + /** + * O(1) check if a specific slot has errors. + * @param nodeLocatorId The node's locator ID + * @param slotName The input slot name to check + * @returns True if the slot has validation errors + */ + const slotHasError = ( + nodeLocatorId: NodeLocatorId, + slotName: string + ): boolean => { + const nodeError = getNodeErrors(nodeLocatorId) + if (!nodeError) return false + + return nodeError.errors.some((e) => e.extra_info?.input_name === slotName) + } + function bindExecutionEvents() { api.addEventListener('execution_start', handleExecutionStart) api.addEventListener('execution_cached', handleExecutionCached) @@ -484,6 +534,9 @@ export const useExecutionStore = defineStore('execution', () => { _executingNodeProgress, // NodeLocatorId conversion helpers executionIdToNodeLocatorId, - nodeLocatorIdToExecutionId + nodeLocatorIdToExecutionId, + // Node error lookup helpers + getNodeErrors, + slotHasError } }) diff --git a/tests-ui/tests/store/executionStore.test.ts b/tests-ui/tests/store/executionStore.test.ts index 5b7c934ab7..d9425113ff 100644 --- a/tests-ui/tests/store/executionStore.test.ts +++ b/tests-ui/tests/store/executionStore.test.ts @@ -106,6 +106,176 @@ describe('executionStore - display_component handling', () => { }) }) +describe('useExecutionStore - Node Error Lookups', () => { + let store: ReturnType + + beforeEach(() => { + vi.clearAllMocks() + setActivePinia(createPinia()) + store = useExecutionStore() + }) + + describe('getNodeErrors', () => { + it('should return undefined when no errors exist', () => { + const result = store.getNodeErrors('123') + expect(result).toBeUndefined() + }) + + it('should return node error by locator ID for root graph node', () => { + // Set up error data + store.lastNodeErrors = { + '123': { + errors: [ + { + type: 'validation_error', + message: 'Invalid input', + details: 'Width must be positive', + extra_info: { input_name: 'width' } + } + ], + class_type: 'TestNode', + dependent_outputs: [] + } + } + + const result = store.getNodeErrors('123') + expect(result).toBeDefined() + expect(result?.errors).toHaveLength(1) + expect(result?.errors[0].message).toBe('Invalid input') + }) + + it('should return node error by locator ID for subgraph node', () => { + const subgraphUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' + const mockSubgraph = { + id: subgraphUuid, + _nodes: [] + } + + const mockNode = { + id: 123, + isSubgraphNode: () => true, + subgraph: mockSubgraph + } as any + + vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode) + + // Set up error data with execution ID + store.lastNodeErrors = { + '123:456': { + errors: [ + { + type: 'validation_error', + message: 'Invalid subgraph input', + details: 'Missing required input', + extra_info: { input_name: 'image' } + } + ], + class_type: 'SubgraphNode', + dependent_outputs: [] + } + } + + // Should find error by converted locator ID + const locatorId = `${subgraphUuid}:456` + const result = store.getNodeErrors(locatorId) + expect(result).toBeDefined() + expect(result?.errors[0].message).toBe('Invalid subgraph input') + }) + }) + + describe('slotHasError', () => { + it('should return false when node has no errors', () => { + const result = store.slotHasError('123', 'width') + expect(result).toBe(false) + }) + + it('should return false when node has errors but slot is not mentioned', () => { + store.lastNodeErrors = { + '123': { + errors: [ + { + type: 'validation_error', + message: 'Invalid input', + details: 'Width must be positive', + extra_info: { input_name: 'width' } + } + ], + class_type: 'TestNode', + dependent_outputs: [] + } + } + + const result = store.slotHasError('123', 'height') + expect(result).toBe(false) + }) + + it('should return true when slot has error', () => { + store.lastNodeErrors = { + '123': { + errors: [ + { + type: 'validation_error', + message: 'Invalid input', + details: 'Width must be positive', + extra_info: { input_name: 'width' } + } + ], + class_type: 'TestNode', + dependent_outputs: [] + } + } + + const result = store.slotHasError('123', 'width') + expect(result).toBe(true) + }) + + it('should return true when multiple errors exist for the same slot', () => { + store.lastNodeErrors = { + '123': { + errors: [ + { + type: 'validation_error', + message: 'Invalid input', + details: 'Width must be positive', + extra_info: { input_name: 'width' } + }, + { + type: 'validation_error', + message: 'Invalid range', + details: 'Width must be less than 1000', + extra_info: { input_name: 'width' } + } + ], + class_type: 'TestNode', + dependent_outputs: [] + } + } + + const result = store.slotHasError('123', 'width') + expect(result).toBe(true) + }) + + it('should handle errors without extra_info', () => { + store.lastNodeErrors = { + '123': { + errors: [ + { + type: 'validation_error', + message: 'General error', + details: 'Something went wrong' + } + ], + class_type: 'TestNode', + dependent_outputs: [] + } + } + + const result = store.slotHasError('123', 'width') + expect(result).toBe(false) + }) + }) +}) + describe('useExecutionStore - NodeLocatorId conversions', () => { let store: ReturnType From 209774527ec509c6a2da89152eff9edddbb996cd Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 11 Nov 2025 12:19:08 -0800 Subject: [PATCH 2/6] show errors walk --- src/scripts/app.ts | 42 +++++++++++++++++++++++--------- src/services/litegraphService.ts | 6 ++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/scripts/app.ts b/src/scripts/app.ts index a716937e27..1f7eb0874a 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1287,9 +1287,10 @@ export class ComfyApp { } /** - * Updates slot.hasErrors flags based on current validation errors. - * Clears all slot errors in the graph hierarchy, then sets hasErrors=true - * for slots that have validation errors in the execution store. + * Updates node.has_errors and slot.hasErrors flags based on current validation errors. + * Clears all errors in the graph hierarchy, then sets errors for nodes/slots that have + * validation errors. Also propagates errors up the subgraph chain (parent subgraph nodes + * get has_errors=true if any descendant has errors). * Call this when lastNodeErrors changes (before queue, after response, in clean()). */ private updateSlotErrorFlags(): void { @@ -1297,8 +1298,9 @@ export class ComfyApp { const executionStore = useExecutionStore() - // Clear all slot errors in entire hierarchy + // Clear all errors in entire hierarchy forEachNode(this.graph, (node) => { + node.has_errors = false if (node.inputs) { for (const slot of node.inputs) { slot.hasErrors = false @@ -1306,7 +1308,7 @@ export class ComfyApp { } }) - // Set slot errors from execution store + // Set errors from execution store if (!executionStore.lastNodeErrors) return for (const [executionId, nodeError] of Object.entries( @@ -1314,16 +1316,32 @@ export class ComfyApp { )) { // Find node by execution ID (handles subgraphs) const node = getNodeByExecutionId(this.graph, executionId) - if (!node || !node.inputs) continue + if (!node) continue + + // Set has_errors on the node itself + node.has_errors = true // Set slot errors for this node - for (const error of nodeError.errors) { - const slotName = error.extra_info?.input_name - if (!slotName) continue + if (node.inputs) { + for (const error of nodeError.errors) { + const slotName = error.extra_info?.input_name + if (!slotName) continue + + const slot = node.inputs.find((s) => s.name === slotName) + if (slot) { + slot.hasErrors = true + } + } + } - const slot = node.inputs.find((s) => s.name === slotName) - if (slot) { - slot.hasErrors = true + // Propagate error up the subgraph chain + // For "123:456:789", also mark "123:456" and "123" as having errors + const parts = executionId.split(':') + for (let i = parts.length - 1; i > 0; i--) { + const parentExecutionId = parts.slice(0, i).join(':') + const parentNode = getNodeByExecutionId(this.graph, parentExecutionId) + if (parentNode) { + parentNode.has_errors = true } } } diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index c4b45097cf..8d13f31e83 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -151,8 +151,7 @@ export const useLitegraphService = () => { } } this.strokeStyles['nodeError'] = function (this: LGraphNode) { - const nodeLocatorId = useWorkflowStore().nodeToNodeLocatorId(this) - if (useExecutionStore().getNodeErrors(nodeLocatorId)) { + if (this.has_errors) { return { color: 'red' } } } @@ -417,8 +416,7 @@ export const useLitegraphService = () => { } } this.strokeStyles['nodeError'] = function (this: LGraphNode) { - const nodeLocatorId = useWorkflowStore().nodeToNodeLocatorId(this) - if (useExecutionStore().getNodeErrors(nodeLocatorId)) { + if (this.has_errors) { return { color: 'red' } } } From e95d8f17fe42553451426b5cf876837efff5e7fa Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 11 Nov 2025 13:44:15 -0800 Subject: [PATCH 3/6] fix double stroke --- src/services/litegraphService.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/services/litegraphService.ts b/src/services/litegraphService.ts index 8d13f31e83..49dd3d5a09 100644 --- a/src/services/litegraphService.ts +++ b/src/services/litegraphService.ts @@ -150,11 +150,6 @@ export const useLitegraphService = () => { return { color: '#0f0' } } } - this.strokeStyles['nodeError'] = function (this: LGraphNode) { - if (this.has_errors) { - return { color: 'red' } - } - } this.strokeStyles['dragOver'] = function (this: LGraphNode) { if (app.dragOverNode?.id == this.id) { return { color: 'dodgerblue' } @@ -415,11 +410,6 @@ export const useLitegraphService = () => { return { color: '#0f0' } } } - this.strokeStyles['nodeError'] = function (this: LGraphNode) { - if (this.has_errors) { - return { color: 'red' } - } - } this.strokeStyles['dragOver'] = function (this: LGraphNode) { if (app.dragOverNode?.id == this.id) { return { color: 'dodgerblue' } From dd6b86fde460fdd55b8c6c4967957ff84952489c Mon Sep 17 00:00:00 2001 From: bymyself Date: Thu, 13 Nov 2025 17:08:30 -0800 Subject: [PATCH 4/6] move to execution store --- src/scripts/app.ts | 65 ------------------------------------ src/stores/executionStore.ts | 59 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 66 deletions(-) diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 1f7eb0874a..a0479b5b28 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1286,67 +1286,6 @@ export class ComfyApp { }) } - /** - * Updates node.has_errors and slot.hasErrors flags based on current validation errors. - * Clears all errors in the graph hierarchy, then sets errors for nodes/slots that have - * validation errors. Also propagates errors up the subgraph chain (parent subgraph nodes - * get has_errors=true if any descendant has errors). - * Call this when lastNodeErrors changes (before queue, after response, in clean()). - */ - private updateSlotErrorFlags(): void { - if (!this.graph) return - - const executionStore = useExecutionStore() - - // Clear all errors in entire hierarchy - forEachNode(this.graph, (node) => { - node.has_errors = false - if (node.inputs) { - for (const slot of node.inputs) { - slot.hasErrors = false - } - } - }) - - // Set errors from execution store - if (!executionStore.lastNodeErrors) return - - for (const [executionId, nodeError] of Object.entries( - executionStore.lastNodeErrors - )) { - // Find node by execution ID (handles subgraphs) - const node = getNodeByExecutionId(this.graph, executionId) - if (!node) continue - - // Set has_errors on the node itself - node.has_errors = true - - // Set slot errors for this node - if (node.inputs) { - for (const error of nodeError.errors) { - const slotName = error.extra_info?.input_name - if (!slotName) continue - - const slot = node.inputs.find((s) => s.name === slotName) - if (slot) { - slot.hasErrors = true - } - } - } - - // Propagate error up the subgraph chain - // For "123:456:789", also mark "123:456" and "123" as having errors - const parts = executionId.split(':') - for (let i = parts.length - 1; i > 0; i--) { - const parentExecutionId = parts.slice(0, i).join(':') - const parentNode = getNodeByExecutionId(this.graph, parentExecutionId) - if (parentNode) { - parentNode.has_errors = true - } - } - } - } - async queuePrompt( number: number, batchCount: number = 1, @@ -1362,7 +1301,6 @@ export class ComfyApp { this.processingQueue = true const executionStore = useExecutionStore() executionStore.lastNodeErrors = null - this.updateSlotErrorFlags() let comfyOrgAuthToken = await useFirebaseAuthStore().getIdToken() let comfyOrgApiKey = useApiKeyAuthStore().getApiKey() @@ -1389,7 +1327,6 @@ export class ComfyApp { delete api.authToken delete api.apiKey executionStore.lastNodeErrors = res.node_errors ?? null - this.updateSlotErrorFlags() if (executionStore.lastNodeErrors?.length) { this.canvas.draw(true, true) } else { @@ -1413,7 +1350,6 @@ export class ComfyApp { if (error instanceof PromptExecutionError) { executionStore.lastNodeErrors = error.response.node_errors ?? null - this.updateSlotErrorFlags() this.canvas.draw(true, true) } break @@ -1714,7 +1650,6 @@ export class ComfyApp { const executionStore = useExecutionStore() executionStore.lastNodeErrors = null executionStore.lastExecutionError = null - this.updateSlotErrorFlags() useDomWidgetStore().clear() diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index c0fbf5edf1..10bbcd8ad0 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import { computed, ref } from 'vue' +import { computed, ref, watch } from 'vue' import type ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue' import { useNodeChatHistory } from '@/composables/node/useNodeChatHistory' @@ -32,6 +32,7 @@ import { app } from '@/scripts/app' import { useNodeOutputStore } from '@/stores/imagePreviewStore' import type { NodeLocatorId } from '@/types/nodeIdentification' import { createNodeLocatorId } from '@/types/nodeIdentification' +import { forEachNode, getNodeByExecutionId } from '@/utils/graphTraversalUtil' interface QueuedPrompt { /** @@ -293,6 +294,62 @@ export const useExecutionStore = defineStore('execution', () => { return nodeError.errors.some((e) => e.extra_info?.input_name === slotName) } + /** + * Automatically update node.has_errors and slot.hasErrors flags when errors change. + * This watcher observes lastNodeErrors and updates the graph node flags reactively. + */ + watch(lastNodeErrors, () => { + if (!app.graph || !app.graph.nodes) return + + // Clear all errors in entire hierarchy + forEachNode(app.graph, (node) => { + node.has_errors = false + if (node.inputs) { + for (const slot of node.inputs) { + slot.hasErrors = false + } + } + }) + + // Set errors from execution store + if (!lastNodeErrors.value) return + + for (const [executionId, nodeError] of Object.entries( + lastNodeErrors.value + )) { + // Find node by execution ID (handles subgraphs) + const node = getNodeByExecutionId(app.graph, executionId) + if (!node) continue + + // Set has_errors on the node itself + node.has_errors = true + + // Set slot errors for this node + if (node.inputs) { + for (const error of nodeError.errors) { + const slotName = error.extra_info?.input_name + if (!slotName) continue + + const slot = node.inputs.find((s) => s.name === slotName) + if (slot) { + slot.hasErrors = true + } + } + } + + // Propagate error up the subgraph chain + // For "123:456:789", also mark "123:456" and "123" as having errors + const parts = executionId.split(':') + for (let i = parts.length - 1; i > 0; i--) { + const parentExecutionId = parts.slice(0, i).join(':') + const parentNode = getNodeByExecutionId(app.graph, parentExecutionId) + if (parentNode) { + parentNode.has_errors = true + } + } + } + }) + function bindExecutionEvents() { api.addEventListener('execution_start', handleExecutionStart) api.addEventListener('execution_cached', handleExecutionCached) From 9ac83d336ddbdec2cecb7f7e9db4eedf341eaf59 Mon Sep 17 00:00:00 2001 From: bymyself Date: Thu, 13 Nov 2025 19:30:48 -0800 Subject: [PATCH 5/6] cleanup comments --- src/stores/executionStore.ts | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index 10bbcd8ad0..8404dd721c 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -245,8 +245,8 @@ export const useExecutionStore = defineStore('execution', () => { }) /** - * Map of locator IDs to node errors (computed once when errors change). - * Converts execution IDs from backend to locator IDs for O(1) lookup. + * Computed map of node errors indexed by locator ID. + * Converts execution IDs from backend to locator IDs for efficient lookup. */ const nodeErrorsByLocatorId = computed>( () => { @@ -257,7 +257,6 @@ export const useExecutionStore = defineStore('execution', () => { for (const [executionId, nodeError] of Object.entries( lastNodeErrors.value )) { - // Convert execution ID to locator ID for subgraph support const locatorId = executionIdToNodeLocatorId(executionId) if (locatorId) { map[locatorId] = nodeError @@ -269,7 +268,7 @@ export const useExecutionStore = defineStore('execution', () => { ) /** - * O(1) lookup for node errors by locator ID. + * Get node errors by locator ID. * Works for both root graph and subgraph nodes. */ const getNodeErrors = ( @@ -279,10 +278,7 @@ export const useExecutionStore = defineStore('execution', () => { } /** - * O(1) check if a specific slot has errors. - * @param nodeLocatorId The node's locator ID - * @param slotName The input slot name to check - * @returns True if the slot has validation errors + * Check if a specific input slot has validation errors. */ const slotHasError = ( nodeLocatorId: NodeLocatorId, @@ -295,13 +291,13 @@ export const useExecutionStore = defineStore('execution', () => { } /** - * Automatically update node.has_errors and slot.hasErrors flags when errors change. - * This watcher observes lastNodeErrors and updates the graph node flags reactively. + * Reactively update node and slot error flags when validation errors change. + * Sets node.has_errors and slot.hasErrors, propagating errors up subgraph chains. */ watch(lastNodeErrors, () => { if (!app.graph || !app.graph.nodes) return - // Clear all errors in entire hierarchy + // Clear all error flags forEachNode(app.graph, (node) => { node.has_errors = false if (node.inputs) { @@ -311,20 +307,18 @@ export const useExecutionStore = defineStore('execution', () => { } }) - // Set errors from execution store if (!lastNodeErrors.value) return + // Set error flags on nodes and slots for (const [executionId, nodeError] of Object.entries( lastNodeErrors.value )) { - // Find node by execution ID (handles subgraphs) const node = getNodeByExecutionId(app.graph, executionId) if (!node) continue - // Set has_errors on the node itself node.has_errors = true - // Set slot errors for this node + // Mark input slots with errors if (node.inputs) { for (const error of nodeError.errors) { const slotName = error.extra_info?.input_name @@ -337,8 +331,7 @@ export const useExecutionStore = defineStore('execution', () => { } } - // Propagate error up the subgraph chain - // For "123:456:789", also mark "123:456" and "123" as having errors + // Propagate errors to parent subgraph nodes const parts = executionId.split(':') for (let i = parts.length - 1; i > 0; i--) { const parentExecutionId = parts.slice(0, i).join(':') From ffdfada4141c9f2073a52d7a0c571f5e427c3fd9 Mon Sep 17 00:00:00 2001 From: bymyself Date: Tue, 18 Nov 2025 11:31:39 -0800 Subject: [PATCH 6/6] refactor --- src/stores/executionStore.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/stores/executionStore.ts b/src/stores/executionStore.ts index 8404dd721c..197d5e439e 100644 --- a/src/stores/executionStore.ts +++ b/src/stores/executionStore.ts @@ -244,10 +244,7 @@ export const useExecutionStore = defineStore('execution', () => { return localId != null ? String(localId) : null }) - /** - * Computed map of node errors indexed by locator ID. - * Converts execution IDs from backend to locator IDs for efficient lookup. - */ + /** Map of node errors indexed by locator ID. */ const nodeErrorsByLocatorId = computed>( () => { if (!lastNodeErrors.value) return {} @@ -267,19 +264,14 @@ export const useExecutionStore = defineStore('execution', () => { } ) - /** - * Get node errors by locator ID. - * Works for both root graph and subgraph nodes. - */ + /** Get node errors by locator ID. */ const getNodeErrors = ( nodeLocatorId: NodeLocatorId ): NodeError | undefined => { return nodeErrorsByLocatorId.value[nodeLocatorId] } - /** - * Check if a specific input slot has validation errors. - */ + /** Check if a specific slot has validation errors. */ const slotHasError = ( nodeLocatorId: NodeLocatorId, slotName: string @@ -291,8 +283,8 @@ export const useExecutionStore = defineStore('execution', () => { } /** - * Reactively update node and slot error flags when validation errors change. - * Sets node.has_errors and slot.hasErrors, propagating errors up subgraph chains. + * Update node and slot error flags when validation errors change. + * Propagates errors up subgraph chains. */ watch(lastNodeErrors, () => { if (!app.graph || !app.graph.nodes) return