feat: show missing node packs in Errors Tab with install support (#9213)

## Summary

Surfaces missing node pack information in the Errors Tab, grouped by
registry pack, with one-click install support via ComfyUI Manager.

## Changes

- **What**: Errors Tab now groups missing nodes by their registry pack
and shows a `MissingPackGroupRow` with pack name, node/pack counts, and
an Install button that triggers Manager installation. A
`MissingNodeCard` shows individual unresolvable nodes that have no
associated pack. `useErrorGroups` was extended to resolve missing node
types to their registry packs using the `/api/workflow/missing_nodes`
endpoint. `executionErrorStore` was refactored to track missing node
types separately from execution errors and expose them reactively.
- **Breaking**: None

## Review Focus

- `useErrorGroups.ts` — the new `resolveMissingNodePacks` logic fetches
pack metadata and maps node types to pack IDs; edge cases around partial
resolution (some nodes have a pack, some don't) produce both
`MissingPackGroupRow` and `MissingNodeCard` entries
- `executionErrorStore.ts` — the store now separates `missingNodeTypes`
state from `errors`; the deferred-warnings path in `app.ts` now calls
`setMissingNodeTypes` so the Errors Tab is populated even when a
workflow loads without executing

## Screenshots (if applicable)


https://github.com/user-attachments/assets/97f8d009-0cac-4739-8740-fd3333b5a85b


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9213-feat-show-missing-node-packs-in-Errors-Tab-with-install-support-3126d73d36508197bc4bf8ebfd2125c8)
by [Unito](https://www.unito.io)
This commit is contained in:
jaeone94
2026-02-26 13:25:47 +09:00
committed by GitHub
parent c24c4ab607
commit 80fe51bb8c
21 changed files with 1075 additions and 151 deletions

View File

@@ -56,6 +56,7 @@ describe('createMissingNodeTypeFromError', () => {
})
expect(result).toEqual({
type: 'MyNodeClass',
nodeId: '42',
hint: '"My Custom Title" (Node ID #42)'
})
})
@@ -68,6 +69,7 @@ describe('createMissingNodeTypeFromError', () => {
})
expect(result).toEqual({
type: 'Unknown',
nodeId: '42',
hint: '"Some Title" (Node ID #42)'
})
})
@@ -84,6 +86,7 @@ describe('createMissingNodeTypeFromError', () => {
})
expect(result).toEqual({
type: 'MyNodeClass',
nodeId: '123',
hint: 'Node ID #123'
})
})

View File

@@ -1,3 +1,4 @@
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { MissingNodeType } from '@/types/comfy'
import type { MissingNodeTypeExtraInfo } from '../types/missingNodeErrorTypes'
@@ -33,5 +34,38 @@ export function createMissingNodeTypeFromError(
const nodeTitle = extraInfo.node_title ?? classType
const hint = buildMissingNodeHint(nodeTitle, classType, extraInfo.node_id)
return hint ? { type: classType, hint } : classType
if (hint) {
return {
type: classType,
...(extraInfo.node_id ? { nodeId: extraInfo.node_id } : {}),
...(hint ? { hint } : {})
}
}
return classType
}
/**
* Extracts the custom node registry ID (cnr_id or aux_id) from a raw
* properties bag.
*
* @param properties - The properties object to inspect
* @returns The cnrId string, or undefined if not found
*/
export function getCnrIdFromProperties(
properties: Record<string, unknown> | undefined | null
): string | undefined {
if (typeof properties?.cnr_id === 'string') return properties.cnr_id
if (typeof properties?.aux_id === 'string') return properties.aux_id
return undefined
}
/**
* Extracts the custom node registry ID (cnr_id or aux_id) from a node's properties.
* Returns undefined if neither property is present.
*
* @param node - The graph node to inspect
* @returns The cnrId string, or undefined if not found
*/
export function getCnrIdFromNode(node: LGraphNode): string | undefined {
return getCnrIdFromProperties(node.properties as Record<string, unknown>)
}