mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
## Summary Refactors the error system to improve separation of concerns, fix DDD layer violations, and address code quality issues. - Extract `missingNodesErrorStore` from `executionErrorStore`, removing the delegation pattern that coupled missing-node logic into the execution error store - Extract `useNodeErrorFlagSync` composable for node error flag reconciliation (previously inlined) - Extract `useErrorClearingHooks` composable with explicit callback cleanup on node removal - Extract `useErrorActions` composable to deduplicate telemetry+command patterns across error card components - Move `getCnrIdFromNode`/`getCnrIdFromProperties` to `platform/nodeReplacement` layer (DDD fix) - Move `missingNodesErrorStore` to `platform/nodeReplacement` (DDD alignment) - Add unmount cancellation guard to `useErrorReport` async `onMounted` - Return watch stop handle from `useNodeErrorFlagSync` - Add `asyncResolvedIds` eviction on `missingNodesError` reset - Add `console.warn` to silent catch blocks and empty array guard - Hoist `useCommandStore` to setup scope, fix floating promises - Add `data-testid` to error groups, image/video error spans, copy button - Update E2E tests to use scoped locators and testids - Add unit tests for `onNodeRemoved` restoration and double-install guard Fixes #9875, Fixes #10027, Fixes #10033, Fixes #10085 ## Test plan - [x] Existing unit tests pass with updated imports and mocks - [x] New unit tests for `useErrorClearingHooks` (callback restoration, double-install guard) - [x] E2E tests updated to use scoped locators and `data-testid` - [ ] Manual: verify error tab shows runtime errors and missing nodes correctly - [ ] Manual: verify "Find on GitHub", "Copy", and "Get Help" buttons work in error cards ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10302-refactor-error-system-cleanup-store-separation-DDD-fix-test-improvements-3286d73d365081838279d045b8dd957a) by [Unito](https://www.unito.io) --------- Co-authored-by: GitHub Action <action@github.com>
112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
import type { Ref } from 'vue'
|
|
import { computed, watch } from 'vue'
|
|
|
|
import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import type { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { app } from '@/scripts/app'
|
|
import type { NodeError } from '@/schemas/apiSchema'
|
|
import { getParentExecutionIds } from '@/types/nodeIdentification'
|
|
import { forEachNode, getNodeByExecutionId } from '@/utils/graphTraversalUtil'
|
|
|
|
function setNodeHasErrors(node: LGraphNode, hasErrors: boolean): void {
|
|
if (node.has_errors === hasErrors) return
|
|
const oldValue = node.has_errors
|
|
node.has_errors = hasErrors
|
|
node.graph?.trigger('node:property:changed', {
|
|
type: 'node:property:changed',
|
|
nodeId: node.id,
|
|
property: 'has_errors',
|
|
oldValue,
|
|
newValue: hasErrors
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Single-pass reconciliation of node error flags.
|
|
* Collects the set of nodes that should have errors, then walks all nodes
|
|
* once, setting each flag exactly once. This avoids the redundant
|
|
* true→false→true transition (and duplicate events) that a clear-then-apply
|
|
* approach would cause.
|
|
*/
|
|
function reconcileNodeErrorFlags(
|
|
rootGraph: LGraph,
|
|
nodeErrors: Record<string, NodeError> | null,
|
|
missingModelExecIds: Set<string>
|
|
): void {
|
|
// Collect nodes and slot info that should be flagged
|
|
// Includes both error-owning nodes and their ancestor containers
|
|
const flaggedNodes = new Set<LGraphNode>()
|
|
const errorSlots = new Map<LGraphNode, Set<string>>()
|
|
|
|
if (nodeErrors) {
|
|
for (const [executionId, nodeError] of Object.entries(nodeErrors)) {
|
|
const node = getNodeByExecutionId(rootGraph, executionId)
|
|
if (!node) continue
|
|
|
|
flaggedNodes.add(node)
|
|
const slotNames = new Set<string>()
|
|
for (const error of nodeError.errors) {
|
|
const name = error.extra_info?.input_name
|
|
if (name) slotNames.add(name)
|
|
}
|
|
if (slotNames.size > 0) errorSlots.set(node, slotNames)
|
|
|
|
for (const parentId of getParentExecutionIds(executionId)) {
|
|
const parentNode = getNodeByExecutionId(rootGraph, parentId)
|
|
if (parentNode) flaggedNodes.add(parentNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const execId of missingModelExecIds) {
|
|
const node = getNodeByExecutionId(rootGraph, execId)
|
|
if (node) flaggedNodes.add(node)
|
|
}
|
|
|
|
forEachNode(rootGraph, (node) => {
|
|
setNodeHasErrors(node, flaggedNodes.has(node))
|
|
|
|
if (node.inputs) {
|
|
const nodeSlotNames = errorSlots.get(node)
|
|
for (const slot of node.inputs) {
|
|
slot.hasErrors = !!nodeSlotNames?.has(slot.name)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
export function useNodeErrorFlagSync(
|
|
lastNodeErrors: Ref<Record<string, NodeError> | null>,
|
|
missingModelStore: ReturnType<typeof useMissingModelStore>
|
|
): () => void {
|
|
const settingStore = useSettingStore()
|
|
const showErrorsTab = computed(() =>
|
|
settingStore.get('Comfy.RightSidePanel.ShowErrorsTab')
|
|
)
|
|
|
|
const stop = watch(
|
|
[
|
|
lastNodeErrors,
|
|
() => missingModelStore.missingModelNodeIds,
|
|
showErrorsTab
|
|
],
|
|
() => {
|
|
if (!app.isGraphReady) return
|
|
// Legacy (LGraphNode) only: suppress missing-model error flags when
|
|
// the Errors tab is hidden, since legacy nodes lack the per-widget
|
|
// red highlight that Vue nodes use to indicate *why* a node has errors.
|
|
// Vue nodes compute hasAnyError independently and are unaffected.
|
|
reconcileNodeErrorFlags(
|
|
app.rootGraph,
|
|
lastNodeErrors.value,
|
|
showErrorsTab.value
|
|
? missingModelStore.missingModelAncestorExecutionIds
|
|
: new Set()
|
|
)
|
|
},
|
|
{ flush: 'post' }
|
|
)
|
|
return stop
|
|
}
|