Files
ComfyUI_frontend/src/components/rightSidePanel/errors/MissingNodeCard.vue
jaeone94 a321d66583 refactor: remove legacy missing nodes dialog (#10102)
## Summary

- Remove the legacy missing nodes modal dialog and migrate all
functionality to the existing Error Overlay / TabErrors system
- Migrate core node version warning from `MissingCoreNodesMessage.vue`
to `MissingNodeCard` in the errors tab
- Remove `Comfy.Workflow.ShowMissingNodesWarning` setting (errors tab
always surfaces missing nodes)
- Delete 6 legacy files: `useMissingNodesDialog.ts`,
`MissingNodesContent.vue`, `MissingNodesFooter.vue`,
`MissingNodesHeader.vue`, `MissingCoreNodesMessage.vue` and its test
- Rename `showMissingNodesDialog`/`showMissingModelsDialog` params to
`showMissingNodes`/`showMissingModels`
- Add `errorOverlay` and `missingNodeCard` to centralized `TestIds`
- Migrate all E2E tests from legacy dialog selectors to error overlay
testIds
- Add new E2E test: MissingNodeCard visible via "See Errors" button flow
- Add new E2E test: subgraph missing node type verified by expanding
pack row
- Add `surfaceMissingNodes` unit tests to `executionErrorStore`
- Guard `semver.compare` against invalid version strings
- Add `role="alert"`, `aria-hidden` for accessibility
- Use reactive props destructuring in `MissingNodeCard` and
`MissingPackGroupRow`

**Net change: -669 lines** (19 files, +323 / -992)


<img width="733" height="579" alt="image"
src="https://github.com/user-attachments/assets/c497809d-b176-43bf-9872-34bd74c6ea0d"
/>


## Test plan

- [x] Unit tests: MissingNodeCard core node warning (7 tests)
- [x] Unit tests: surfaceMissingNodes (4 tests)
- [x] Unit tests: workflowService showPendingWarnings (updated)
- [x] E2E: Error overlay visible on missing nodes workflow
- [x] E2E: Error overlay visible on subgraph missing nodes
- [x] E2E: MissingNodeCard visible via See Errors button
- [x] E2E: Subgraph node type visible after expanding pack row
- [x] E2E: Error overlay does not resurface on undo/redo
- [x] E2E: Error overlay does not reappear on workflow tab switch
- [x] Typecheck, lint, knip all passing

## Related issues

- Closes #9923 (partially — `errorOverlay` and `missingNodeCard` added
to TestIds)
- References #10027 (mock hoisting inconsistency)
- References #10033 (i18n-based test selectors)
- References #10085 (DDD layer violation + focusedErrorNodeId)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10102-refactor-remove-legacy-missing-nodes-dialog-3256d73d365081c194d2e90bc6401846)
by [Unito](https://www.unito.io)
2026-03-17 11:14:44 +09:00

184 lines
6.1 KiB
Vue

<template>
<div data-testid="missing-node-card" class="px-4 pb-2">
<!-- Core node version warning (OSS only) -->
<div
v-if="!isCloud && hasMissingCoreNodes"
role="alert"
class="mb-3 flex gap-2.5 rounded-lg border border-warning-background/30 bg-warning-background/10 p-3"
>
<i
aria-hidden="true"
class="mt-0.5 icon-[lucide--triangle-alert] size-4 shrink-0 text-warning-background"
/>
<div class="flex flex-col gap-1.5 text-xs/relaxed text-muted-foreground">
<p class="m-0">
{{
currentComfyUIVersion
? t('loadWorkflowWarning.outdatedVersion', {
version: currentComfyUIVersion
})
: t('loadWorkflowWarning.outdatedVersionGeneric')
}}
</p>
<div
v-for="[version, nodes] in sortedMissingCoreNodes"
:key="version"
class="ml-2"
>
<span class="font-medium">
{{
t('loadWorkflowWarning.coreNodesFromVersion', {
version: version || t('loadWorkflowWarning.unknownVersion')
})
}}
</span>
<span class="ml-1">{{ getUniqueNodeNames(nodes).join(', ') }}</span>
</div>
</div>
</div>
<!-- Sub-label: cloud or OSS message shown above all pack groups -->
<p
class="m-0 text-sm/relaxed text-muted-foreground"
:class="showManagerHint ? 'pb-3' : 'pb-5'"
>
{{
isCloud
? t('rightSidePanel.missingNodePacks.cloudMessage')
: t('rightSidePanel.missingNodePacks.ossMessage')
}}
</p>
<!-- Manager disabled hint: shown on OSS when manager is not active -->
<i18n-t
v-if="showManagerHint"
keypath="rightSidePanel.missingNodePacks.ossManagerDisabledHint"
tag="p"
class="m-0 pb-5 text-sm/relaxed text-muted-foreground"
>
<template #pipCmd>
<code
class="rounded-sm bg-comfy-menu-bg px-1 py-0.5 font-mono text-xs text-comfy-input-foreground"
>pip install -U --pre comfyui-manager</code
>
</template>
<template #flag>
<code
class="rounded-sm bg-comfy-menu-bg px-1 py-0.5 font-mono text-xs text-comfy-input-foreground"
>--enable-manager</code
>
</template>
</i18n-t>
<MissingPackGroupRow
v-for="group in missingPackGroups"
:key="group.packId ?? '__unknown__'"
:group="group"
:show-info-button="showInfoButton"
:show-node-id-badge="showNodeIdBadge"
@locate-node="emit('locateNode', $event)"
@open-manager-info="emit('openManagerInfo', $event)"
/>
</div>
<!-- Apply Changes: shown when manager enabled and at least one pack install succeeded -->
<div v-if="shouldShowManagerButtons" class="px-4">
<Button
v-if="hasInstalledPacksPendingRestart"
variant="primary"
:disabled="isRestarting"
class="mt-2 h-9 w-full justify-center gap-2 text-sm font-semibold"
@click="applyChanges()"
>
<DotSpinner v-if="isRestarting" duration="1s" :size="14" />
<i
v-else
aria-hidden="true"
class="icon-[lucide--refresh-cw] size-4 shrink-0"
/>
<span class="min-w-0 truncate">{{
t('rightSidePanel.missingNodePacks.applyChanges')
}}</span>
</Button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { compare, valid } from 'semver'
import Button from '@/components/ui/button/Button.vue'
import DotSpinner from '@/components/common/DotSpinner.vue'
import { useApplyChanges } from '@/workbench/extensions/manager/composables/useApplyChanges'
import { useComfyManagerStore } from '@/workbench/extensions/manager/stores/comfyManagerStore'
import { isCloud } from '@/platform/distribution/types'
import { useManagerState } from '@/workbench/extensions/manager/composables/useManagerState'
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { MissingPackGroup } from '@/components/rightSidePanel/errors/useErrorGroups'
import MissingPackGroupRow from '@/components/rightSidePanel/errors/MissingPackGroupRow.vue'
const { showInfoButton, showNodeIdBadge, missingPackGroups } = defineProps<{
showInfoButton: boolean
showNodeIdBadge: boolean
missingPackGroups: MissingPackGroup[]
}>()
const emit = defineEmits<{
locateNode: [nodeId: string]
openManagerInfo: [packId: string]
}>()
const { t } = useI18n()
const { missingCoreNodes } = useMissingNodes()
const systemStatsStore = useSystemStatsStore()
const hasMissingCoreNodes = computed(
() => Object.keys(missingCoreNodes.value).length > 0
)
const currentComfyUIVersion = computed<string | null>(() => {
if (!hasMissingCoreNodes.value) return null
return systemStatsStore.systemStats?.system?.comfyui_version ?? null
})
const sortedMissingCoreNodes = computed(() =>
Object.entries(missingCoreNodes.value).sort(([a], [b]) => {
const aValid = valid(a)
const bValid = valid(b)
if (!aValid && !bValid) return 0
if (!aValid) return 1
if (!bValid) return -1
return compare(b, a)
})
)
function getUniqueNodeNames(nodes: LGraphNode[]): string[] {
const types = new Set(nodes.map((node) => node.type).filter(Boolean))
return [...types].sort()
}
const comfyManagerStore = useComfyManagerStore()
const { isRestarting, applyChanges } = useApplyChanges()
const { shouldShowManagerButtons } = useManagerState()
/**
* Show the --enable-manager hint when:
* - Not on Cloud (OSS/local only)
* - Manager is disabled (showInfoButton is false)
*/
const showManagerHint = computed(() => !isCloud && !showInfoButton)
/**
* Show Apply Changes when any pack from the error group is already installed
* on disk but ComfyUI hasn't restarted yet to load it.
* This is server-state based → persists across browser refreshes.
*/
const hasInstalledPacksPendingRestart = computed(() =>
missingPackGroups.some(
(g) => g.packId !== null && comfyManagerStore.isPackInstalled(g.packId)
)
)
</script>