Cleanup app.graph usage (#7399)

Prior to the release of subgraphs, there was a single graph accessed
through `app.graph`. Now that there's multiple graphs, there's a lot of
code that needs to be reviewed and potentially updated depending on if
it cares about nearby nodes, all nodes, or something else requiring
specific attention.

This was done by simply changing the type of `app.graph` to unknown so
the typechecker will complain about every place it's currently used.
References were then updated to `app.rootGraph` if the previous usage
was correct, or actually rewritten.

By not getting rid of `app.graph`, this change already ensures that
there's no loss of functionality for custom nodes, but the prior typing
of `app.graph` can always be restored if future dissuasion of
`app.graph` usage creates issues.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7399-Cleanup-app-graph-usage-2c76d73d365081178743dfdcf07f44d0)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2025-12-11 22:37:34 -08:00
committed by GitHub
parent 88bdc605a7
commit f2a0e5102e
39 changed files with 192 additions and 209 deletions

View File

@@ -58,7 +58,7 @@ const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()

View File

@@ -83,7 +83,7 @@ const props = withDefaults(defineProps<Props>(), {
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
graphHasMissingNodes(app.rootGraph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()

View File

@@ -128,7 +128,7 @@ onMounted(async () => {
reportContent.value = generateErrorReport({
systemStats: systemStatsStore.systemStats!,
serverLogs: logs,
workflow: app.graph.serialize(),
workflow: app.rootGraph.serialize(),
exceptionType: error.exceptionType,
exceptionMessage: error.exceptionMessage,
traceback: error.traceback,

View File

@@ -159,6 +159,7 @@ import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isNativeWindow } from '@/utils/envUtil'
import { forEachNode } from '@/utils/graphTraversalUtil'
import SelectionRectangle from './SelectionRectangle.vue'
@@ -269,20 +270,18 @@ watch(
() => {
if (!canvasStore.canvas) return
for (const n of comfyApp.graph.nodes) {
if (!n.widgets) continue
forEachNode(comfyApp.rootGraph, (n) => {
if (!n.widgets) return
for (const w of n.widgets) {
if (w[IS_CONTROL_WIDGET]) {
updateControlWidgetLabel(w)
if (w.linkedWidgets) {
for (const l of w.linkedWidgets) {
updateControlWidgetLabel(l)
}
}
if (!w[IS_CONTROL_WIDGET]) continue
updateControlWidgetLabel(w)
if (!w.linkedWidgets) continue
for (const l of w.linkedWidgets) {
updateControlWidgetLabel(l)
}
}
}
comfyApp.graph.setDirtyCanvas(true)
})
canvasStore.canvas.setDirty(true)
}
)
@@ -332,7 +331,7 @@ watch(
}
// Force canvas redraw to ensure progress updates are visible
canvas.graph.setDirtyCanvas(true, false)
canvas.setDirty(true, false)
},
{ deep: true }
)
@@ -344,7 +343,7 @@ watch(
(lastNodeErrors) => {
if (!comfyApp.graph) return
for (const node of comfyApp.graph.nodes) {
forEachNode(comfyApp.rootGraph, (node) => {
// Clear existing errors
for (const slot of node.inputs) {
delete slot.hasErrors
@@ -354,7 +353,7 @@ watch(
}
const nodeErrors = lastNodeErrors?.[node.id]
if (!nodeErrors) continue
if (!nodeErrors) return
const validErrors = nodeErrors.errors.filter(
(error) => error.extra_info?.input_name !== undefined
@@ -367,9 +366,9 @@ watch(
node.inputs[inputIndex].hasErrors = true
}
})
}
})
comfyApp.canvas.draw(true, true)
comfyApp.canvas.setDirty(true, true)
}
)

View File

@@ -58,7 +58,7 @@ const onEdit = (newValue: string) => {
target.subgraph.name = trimmedTitle
}
app.graph.setDirtyCanvas(true, true)
app.canvas.setDirty(true, true)
}
showInput.value = false
titleEditorStore.titleEditorTarget = null

View File

@@ -33,7 +33,7 @@ const toast = useToast()
const workflowStore = useWorkflowStore()
const migrateToLitegraphReroute = async () => {
const workflowJSON = app.graph.serialize() as unknown as WorkflowJSON04
const workflowJSON = app.rootGraph.serialize() as unknown as WorkflowJSON04
const migratedWorkflowJSON = migrateLegacyRerouteNodes(workflowJSON)
await app.loadGraphData(
migratedWorkflowJSON,

View File

@@ -51,7 +51,7 @@ export function useMaskEditorSaver() {
updateNodeWithServerReferences(sourceNode, outputData)
app.graph.setDirtyCanvas(true)
app.canvas.setDirty(true)
} catch (error) {
console.error('[MaskEditorSaver] Save failed:', error)
throw error
@@ -308,7 +308,7 @@ export function useMaskEditorSaver() {
const mainImg = await loadImageFromUrl(dataUrl)
node.imgs = [mainImg]
app.graph.setDirtyCanvas(true)
app.canvas.setDirty(true)
}
function updateNodeWithServerReferences(

View File

@@ -55,7 +55,7 @@ export const useNodeBadge = () => {
showApiPricingBadge
],
() => {
app.graph?.setDirtyCanvas(true, true)
app.canvas?.setDirty(true, true)
}
)

View File

@@ -41,7 +41,7 @@ export const useCanvasDrop = (canvasRef: Ref<HTMLCanvasElement | null>) => {
} else if (node.data instanceof ComfyModelDef) {
const model = node.data
const pos = basePos
const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1])
const nodeAtPos = comfyApp.canvas.graph?.getNodeOnPos(pos[0], pos[1])
let targetProvider: ModelNodeProvider | null = null
let targetGraphNode: LGraphNode | null = null
if (nodeAtPos) {

View File

@@ -104,7 +104,7 @@ export function useCoreCommands(): ComfyCommand[] {
menubarLabel: 'New',
category: 'essentials' as const,
function: async () => {
const previousWorkflowHadNodes = app.graph._nodes.length > 0
const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0
await workflowService.loadBlankWorkflow()
telemetry?.trackWorkflowCreated({
workflow_type: 'blank',
@@ -127,7 +127,7 @@ export function useCoreCommands(): ComfyCommand[] {
icon: 'pi pi-code',
label: 'Load Default Workflow',
function: async () => {
const previousWorkflowHadNodes = app.graph._nodes.length > 0
const previousWorkflowHadNodes = app.rootGraph._nodes.length > 0
await workflowService.loadDefaultWorkflow()
telemetry?.trackWorkflowCreated({
workflow_type: 'default',
@@ -705,7 +705,7 @@ export function useCoreCommands(): ComfyCommand[] {
'Comfy.GroupSelectedNodes.Padding'
)
group.resizeTo(group.children, padding)
app.graph.change()
app.canvas.setDirty(false, true)
}
}
}

View File

@@ -47,8 +47,8 @@ const Workflow = {
const id = `${PREFIX}${SEPARATOR}${name}`
// Check if lready registered/in use in this workflow
// @ts-expect-error fixme ts strict error
if (app.graph.extra?.groupNodes?.[name]) {
if (app.graph.nodes.find((n) => n.type === id)) {
if (app.rootGraph.extra?.groupNodes?.[name]) {
if (app.rootGraph.nodes.find((n) => n.type === id)) {
return Workflow.InUse.InWorkflow
} else {
return Workflow.InUse.Registered
@@ -57,8 +57,8 @@ const Workflow = {
return Workflow.InUse.Free
},
storeGroupNode(name: string, data: GroupNodeWorkflowData) {
let extra = app.graph.extra
if (!extra) app.graph.extra = extra = {}
let extra = app.rootGraph.extra
if (!extra) app.rootGraph.extra = extra = {}
let groupNodes = extra.groupNodes
if (!groupNodes) extra.groupNodes = groupNodes = {}
// @ts-expect-error fixme ts strict error
@@ -118,7 +118,7 @@ class GroupNodeBuilder {
sortNodes() {
// Gets the builders nodes in graph execution order
const nodesInOrder = app.graph.computeExecutionOrder(false)
const nodesInOrder = app.rootGraph.computeExecutionOrder(false)
this.nodes = this.nodes
.map((node) => ({ index: nodesInOrder.indexOf(node), node }))
// @ts-expect-error id might be string
@@ -131,7 +131,7 @@ class GroupNodeBuilder {
const storeLinkTypes = (config) => {
// Store link types for dynamically typed nodes e.g. reroutes
for (const link of config.links) {
const origin = app.graph.getNodeById(link[4])
const origin = app.rootGraph.getNodeById(link[4])
// @ts-expect-error fixme ts strict error
const type = origin.outputs[link[1]].type
link.push(type)
@@ -151,7 +151,7 @@ class GroupNodeBuilder {
let type = output.type
if (!output.links?.length) continue
for (const l of output.links) {
const link = app.graph.links[l]
const link = app.rootGraph.links[l]
if (!link) continue
if (type === '*') type = link.type
@@ -853,7 +853,7 @@ export class GroupNodeHandler {
// The inner node is connected via the group node inputs
const linkId = this.node.inputs[externalSlot].link
// @ts-expect-error fixme ts strict error
let link = app.graph.links[linkId]
let link = app.rootGraph.links[linkId]
// Use the outer link, but update the target to the inner node
link = {
@@ -980,7 +980,7 @@ export class GroupNodeHandler {
// @ts-expect-error fixme ts strict error
groupNode[GROUP].populateWidgets()
// @ts-expect-error fixme ts strict error
app.graph.add(groupNode)
app.rootGraph.add(groupNode)
// @ts-expect-error fixme ts strict error
groupNode.setSize([
// @ts-expect-error fixme ts strict error
@@ -1032,7 +1032,7 @@ export class GroupNodeHandler {
const newNodes = []
for (let i = 0; i < selectedIds.length; i++) {
const id = selectedIds[i]
const newNode = app.graph.getNodeById(id)
const newNode = app.rootGraph.getNodeById(id)
const innerNode = innerNodes[i]
newNodes.push(newNode)
@@ -1111,17 +1111,17 @@ export class GroupNodeHandler {
const reconnectInputs = (selectedIds) => {
for (const innerNodeIndex in this.groupData.oldToNewInputMap) {
const id = selectedIds[innerNodeIndex]
const newNode = app.graph.getNodeById(id)
const newNode = app.rootGraph.getNodeById(id)
const map = this.groupData.oldToNewInputMap[innerNodeIndex]
for (const innerInputId in map) {
const groupSlotId = map[innerInputId]
if (groupSlotId == null) continue
const slot = node.inputs[groupSlotId]
if (slot.link == null) continue
const link = app.graph.links[slot.link]
const link = app.rootGraph.links[slot.link]
if (!link) continue
// connect this node output to the input of another node
const originNode = app.graph.getNodeById(link.origin_id)
const originNode = app.rootGraph.getNodeById(link.origin_id)
// @ts-expect-error fixme ts strict error
originNode.connect(link.origin_slot, newNode, +innerInputId)
}
@@ -1140,9 +1140,11 @@ export class GroupNodeHandler {
const links = [...output.links]
for (const l of links) {
const slot = this.groupData.newToOldOutputMap[groupOutputId]
const link = app.graph.links[l]
const targetNode = app.graph.getNodeById(link.target_id)
const newNode = app.graph.getNodeById(selectedIds[slot.node.index])
const link = app.rootGraph.links[l]
const targetNode = app.rootGraph.getNodeById(link.target_id)
const newNode = app.rootGraph.getNodeById(
selectedIds[slot.node.index]
)
// @ts-expect-error fixme ts strict error
newNode.connect(slot.slot, targetNode, link.target_slot)
}
@@ -1155,7 +1157,7 @@ export class GroupNodeHandler {
const { newNodes, selectedIds } = addInnerNodes()
reconnectInputs(selectedIds)
reconnectOutputs(selectedIds)
app.graph.remove(this.node)
app.rootGraph.remove(this.node)
return newNodes
} finally {
@@ -1291,7 +1293,7 @@ export class GroupNodeHandler {
const handler = ({ detail }) => {
const id = getId(detail)
if (!id) return
const node = app.graph.getNodeById(id)
const node = app.rootGraph.getNodeById(id)
if (node) return
// @ts-expect-error fixme ts strict error
@@ -1546,7 +1548,7 @@ export class GroupNodeHandler {
}
this.linkOutputs(node, i)
app.graph.remove(node)
app.rootGraph.remove(node)
// Set internal ID to what is expected after workflow is reloaded
node.id = `${this.node.id}:${i}`
@@ -1565,10 +1567,10 @@ export class GroupNodeHandler {
// Clone the links as they'll be changed if we reconnect
const links = [...output.links]
for (const l of links) {
const link = app.graph.links[l]
const link = app.rootGraph.links[l]
if (!link) continue
const targetNode = app.graph.getNodeById(link.target_id)
const targetNode = app.rootGraph.getNodeById(link.target_id)
const newSlot =
this.groupData.oldToNewOutputMap[nodeId]?.[link.origin_slot]
if (newSlot != null) {
@@ -1582,7 +1584,7 @@ export class GroupNodeHandler {
linkInputs() {
for (const link of this.groupData.nodeData.links ?? []) {
const [, originSlot, targetId, targetSlot, actualOriginId] = link
const originNode = app.graph.getNodeById(actualOriginId)
const originNode = app.rootGraph.getNodeById(actualOriginId)
if (!originNode) continue // this node is in the group
originNode.connect(
originSlot,
@@ -1621,7 +1623,7 @@ export class GroupNodeHandler {
// @ts-expect-error fixme ts strict error
groupNode[GROUP].populateWidgets()
// @ts-expect-error fixme ts strict error
app.graph.add(groupNode)
app.rootGraph.add(groupNode)
// Remove all converted nodes and relink them
// @ts-expect-error fixme ts strict error
@@ -1802,7 +1804,7 @@ const ext: ComfyExtension = {
// Re-register group nodes so new ones are created with the correct options
// @ts-expect-error fixme ts strict error
Object.assign(globalDefs, defs)
const nodes = app.graph.extra?.groupNodes
const nodes = app.rootGraph.extra?.groupNodes
if (nodes) {
await GroupNodeConfig.registerFromWorkflow(nodes, {})
}

View File

@@ -270,7 +270,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
this.groupData.oldToNewWidgetMap[this.selectedNodeInnerIndex]
const items = Object.keys(widgets ?? {})
// @ts-expect-error fixme ts strict error
const type = app.graph.extra.groupNodes[this.selectedGroup]
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
const config = type.config?.[this.selectedNodeInnerIndex]?.input
this.widgetsPage.replaceChildren(
...items.map((oldName) => {
@@ -290,7 +290,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
const inputs = this.groupData.nodeInputs[this.selectedNodeInnerIndex]
const items = Object.keys(inputs ?? {})
// @ts-expect-error fixme ts strict error
const type = app.graph.extra.groupNodes[this.selectedGroup]
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
const config = type.config?.[this.selectedNodeInnerIndex]?.input
this.inputsPage.replaceChildren(
// @ts-expect-error fixme ts strict error
@@ -324,7 +324,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
this.groupData.oldToNewOutputMap[this.selectedNodeInnerIndex]
// @ts-expect-error fixme ts strict error
const type = app.graph.extra.groupNodes[this.selectedGroup]
const type = app.rootGraph.extra.groupNodes[this.selectedGroup]
const config = type.config?.[this.selectedNodeInnerIndex]?.output
const node = this.groupData.nodeData.nodes[this.selectedNodeInnerIndex]
const checkable = node.type !== 'PrimitiveNode'
@@ -355,7 +355,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
// @ts-expect-error fixme ts strict error
show(type?) {
const groupNodes = Object.keys(app.graph.extra?.groupNodes ?? {}).sort(
const groupNodes = Object.keys(app.rootGraph.extra?.groupNodes ?? {}).sort(
(a, b) => a.localeCompare(b)
)
@@ -425,7 +425,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
'button.comfy-btn',
{
onclick: () => {
const node = app.graph.nodes.find(
const node = app.rootGraph.nodes.find(
(n) => n.type === `${PREFIX}${SEPARATOR}` + this.selectedGroup
)
if (node) {
@@ -440,7 +440,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
)
) {
// @ts-expect-error fixme ts strict error
delete app.graph.extra.groupNodes[this.selectedGroup]
delete app.rootGraph.extra.groupNodes[this.selectedGroup]
LiteGraph.unregisterNodeType(
`${PREFIX}${SEPARATOR}` + this.selectedGroup
)
@@ -459,7 +459,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
const types = {}
for (const g in this.modifications) {
// @ts-expect-error fixme ts strict error
const type = app.graph.extra.groupNodes[g]
const type = app.rootGraph.extra.groupNodes[g]
let config = (type.config ??= {})
let nodeMods = this.modifications[g]?.nodes
@@ -515,7 +515,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
types[g] = type
if (!nodesByType) {
nodesByType = app.graph.nodes.reduce((p, n) => {
nodesByType = app.rootGraph.nodes.reduce((p, n) => {
// @ts-expect-error fixme ts strict error
p[n.type] ??= []
// @ts-expect-error fixme ts strict error
@@ -536,7 +536,7 @@ export class ManageGroupDialog extends ComfyDialog<HTMLDialogElement> {
}
this.modifications = {}
this.app.graph.setDirtyCanvas(true, true)
this.app.canvas.setDirty(true, true)
this.changeGroup(this.selectedGroup, false)
}
},

View File

@@ -367,7 +367,7 @@ const ext: ComfyExtension = {
data = JSON.parse(data || '{}')
const nodeIds = Object.keys(app.canvas.selected_nodes)
for (let i = 0; i < nodeIds.length; i++) {
const node = app.graph.getNodeById(nodeIds[i])
const node = app.canvas.graph?.getNodeById(nodeIds[i])
const nodeData = node?.constructor.nodeData
let groupData = GroupNodeHandler.getGroupData(node)

View File

@@ -142,7 +142,7 @@ app.registerExtension({
onNodeOutputsUpdated(nodeOutputs: Record<NodeLocatorId, any>) {
for (const [nodeLocatorId, output] of Object.entries(nodeOutputs)) {
if ('audio' in output) {
const node = getNodeByLocatorId(app.graph, nodeLocatorId)
const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId)
if (!node) continue
// @ts-expect-error fixme ts strict error

View File

@@ -97,7 +97,7 @@ app.registerExtension({
const img = new Image()
img.onload = () => {
node.imgs = [img]
app.graph.setDirtyCanvas(true)
app.canvas.setDirty(true)
}
img.src = data
}

View File

@@ -433,7 +433,7 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
}
const nodeCounts = reduceAllNodes<NodeMetrics>(
app.graph,
app.rootGraph,
(metrics, node) => {
const nodeDef = nodeDefStore.nodeDefsByName[node.type]
const isCustomNode =

View File

@@ -633,6 +633,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
return getSubgraphsFromInstanceIds(subgraph, subgraphNodeIds, subgraphs)
}
//FIXME: use existing util function
const executionIdToCurrentId = (id: string) => {
const subgraph = activeSubgraph.value
@@ -647,7 +648,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
const subgraphNodeIds = id.split(':')
// Start from the root graph
const { graph } = comfyApp
const graph = comfyApp.rootGraph
// If the last subgraph is the active subgraph, return the node ID
const subgraphs = getSubgraphsFromInstanceIds(graph, subgraphNodeIds)
@@ -714,7 +715,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
try {
const subgraphs = getSubgraphsFromInstanceIds(
comfyApp.graph,
comfyApp.rootGraph,
subgraphNodeIds.map((id) => String(id))
)
const immediateSubgraph = subgraphs[subgraphs.length - 1]
@@ -779,7 +780,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
return null
}
const path = findSubgraphPath(comfyApp.graph, subgraphUuid)
const path = findSubgraphPath(comfyApp.rootGraph, subgraphUuid)
if (!path) return null
// If we have a target subgraph, check if the path goes through it
@@ -787,7 +788,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
targetSubgraph &&
!path.some((_, idx) => {
const subgraphs = getSubgraphsFromInstanceIds(
comfyApp.graph,
comfyApp.rootGraph,
path.slice(0, idx + 1).map((id) => String(id))
)
return subgraphs[subgraphs.length - 1] === targetSubgraph

View File

@@ -44,7 +44,7 @@ export function useWorkflowPersistence() {
const persistCurrentWorkflow = () => {
if (!workflowPersistenceEnabled.value) return
const workflow = JSON.stringify(comfyApp.graph.serialize())
const workflow = JSON.stringify(comfyApp.rootGraph.serialize())
try {
localStorage.setItem('workflow', workflow)

View File

@@ -436,7 +436,7 @@ const handleEnterSubgraph = () => {
useTelemetry()?.trackUiButtonClicked({
button_id: 'graph_node_open_subgraph_clicked'
})
const graph = app.graph?.rootGraph || app.graph
const graph = app.rootGraph
if (!graph) {
console.warn('LGraphNode: No graph available for subgraph navigation')
return
@@ -468,9 +468,7 @@ const nodeOutputLocatorId = computed(() =>
const lgraphNode = computed(() => {
const locatorId = getLocatorIdFromNodeData(nodeData)
const rootGraph = app.graph?.rootGraph || app.graph
if (!rootGraph) return null
return getNodeByLocatorId(rootGraph, locatorId)
return getNodeByLocatorId(app.rootGraph, locatorId)
})
const nodeMedia = computed(() => {

View File

@@ -243,7 +243,7 @@ const isSubgraphNode = computed(() => {
if (!nodeData?.id) return false
// Get the underlying LiteGraph node
const graph = app.graph?.rootGraph || app.graph
const graph = app.rootGraph
if (!graph) return false
const locatorId = getLocatorIdFromNodeData(nodeData)

View File

@@ -39,8 +39,8 @@ defineEmits<{
// Get litegraph node
const litegraphNode = computed(() => {
if (!props.nodeId || !app.rootGraph) return null
return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null
if (!props.nodeId || !app.canvas.graph) return null
return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null
})
// Check if this is an output node (PreviewAudio, SaveAudio, etc)

View File

@@ -155,8 +155,8 @@ const isWaveformActive = computed(() => isRecording.value || isPlaying.value)
const modelValue = defineModel<string>({ default: '' })
const litegraphNode = computed(() => {
if (!props.nodeId || !app.rootGraph) return null
return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null
if (!props.nodeId || !app.canvas.graph) return null
return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null
})
async function handleRecordingComplete(blob: Blob) {

View File

@@ -185,8 +185,8 @@ const showVolumeTwo = computed(() => !isMuted.value && volume.value > 0.5)
const showVolumeOne = computed(() => isMuted.value && volume.value > 0)
const litegraphNode = computed(() => {
if (!props.nodeId || !app.rootGraph) return null
return app.rootGraph.getNodeById(props.nodeId) as LGraphNode | null
if (!props.nodeId || !app.canvas.graph) return null
return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null
})
const hidden = computed(() => {

View File

@@ -64,7 +64,7 @@ import type { ComfyExtension, MissingNodeType } from '@/types/comfy'
import { type ExtensionManager } from '@/types/extensionTypes'
import type { NodeExecutionId } from '@/types/nodeIdentification'
import { graphToPrompt } from '@/utils/executionUtil'
import { forEachNode } from '@/utils/graphTraversalUtil'
import { collectAllNodes, forEachNode } from '@/utils/graphTraversalUtil'
import {
getNodeByExecutionId,
triggerCallbackOnAllNodes
@@ -157,15 +157,15 @@ export class ComfyApp {
// TODO: Migrate internal usage to the
/** @deprecated Use {@link rootGraph} instead */
get graph() {
get graph(): unknown {
return this.rootGraphInternal!
}
get rootGraph(): LGraph | undefined {
get rootGraph(): LGraph {
if (!this.rootGraphInternal) {
console.error('ComfyApp graph accessed before initialization')
}
return this.rootGraphInternal
return this.rootGraphInternal!
}
// @ts-expect-error fixme ts strict error
@@ -512,7 +512,7 @@ export class ComfyApp {
}
}
app.graph.setDirtyCanvas(true)
app.canvas.setDirty(true)
useNodeOutputStore().updateNodeImages(node)
}
@@ -553,7 +553,7 @@ export class ComfyApp {
useEventListener(this.canvasElRef, 'dragleave', async () => {
if (!this.dragOverNode) return
this.dragOverNode = null
this.graph.setDirtyCanvas(false, true)
this.canvas.setDirty(false, true)
})
// Add handler for dropping onto a specific node
@@ -562,7 +562,10 @@ export class ComfyApp {
'dragover',
(event: DragEvent) => {
this.canvas.adjustMouseEvent(event)
const node = this.graph.getNodeOnPos(event.canvasX, event.canvasY)
const node = this.canvas.graph?.getNodeOnPos(
event.canvasX,
event.canvasY
)
if (!node?.onDragOver?.(event)) {
this.dragOverNode = null
@@ -573,7 +576,7 @@ export class ComfyApp {
// dragover event is fired very frequently, run this on an animation frame
requestAnimationFrame(() => {
this.graph.setDirtyCanvas(false, true)
this.canvas.setDirty(false, true)
})
},
false
@@ -638,11 +641,11 @@ export class ComfyApp {
})
api.addEventListener('progress', () => {
this.graph.setDirtyCanvas(true, false)
this.canvas.setDirty(true, false)
})
api.addEventListener('executing', () => {
this.graph.setDirtyCanvas(true, false)
this.canvas.setDirty(true, false)
})
api.addEventListener('executed', ({ detail }) => {
@@ -653,14 +656,14 @@ export class ComfyApp {
merge: detail.merge
})
const node = getNodeByExecutionId(this.graph, executionId)
const node = getNodeByExecutionId(this.rootGraph, executionId)
if (node && node.onExecuted) {
node.onExecuted(detail.output)
}
})
api.addEventListener('execution_start', () => {
triggerCallbackOnAllNodes(this.graph, 'onExecutionStart')
triggerCallbackOnAllNodes(this.rootGraph, 'onExecutionStart')
})
api.addEventListener('execution_error', ({ detail }) => {
@@ -844,7 +847,7 @@ export class ComfyApp {
registerProxyWidgets(this.canvas)
this.graph.start()
this.rootGraph.start()
// Ensure the canvas fills the window
useResizeObserver(this.canvasElRef, ([canvasEl]) => {
@@ -1194,17 +1197,18 @@ export class ComfyApp {
try {
// @ts-expect-error Discrepancies between zod and litegraph - in progress
this.graph.configure(graphData)
this.rootGraph.configure(graphData)
// Save original renderer version before scaling (it gets modified during scaling)
const originalMainGraphRenderer = this.graph.extra.workflowRendererVersion
const originalMainGraphRenderer =
this.rootGraph.extra.workflowRendererVersion
// Scale main graph
ensureCorrectLayoutScale(originalMainGraphRenderer)
// Scale all subgraphs that were loaded with the workflow
// Use original main graph renderer as fallback (not the modified one)
for (const subgraph of this.graph.subgraphs.values()) {
for (const subgraph of this.rootGraph.subgraphs.values()) {
ensureCorrectLayoutScale(
subgraph.extra.workflowRendererVersion || originalMainGraphRenderer,
subgraph
@@ -1235,7 +1239,7 @@ export class ComfyApp {
console.error(error)
return
}
for (const node of this.graph.nodes) {
forEachNode(this.rootGraph, (node) => {
const size = node.computeSize()
size[0] = Math.max(node.size[0], size[0])
size[1] = Math.max(node.size[1], size[1])
@@ -1284,7 +1288,7 @@ export class ComfyApp {
}
useExtensionService().invokeExtensions('loadedGraphNode', node)
}
})
if (missingNodeTypes.length && showMissingNodesDialog) {
this.showMissingNodesError(missingNodeTypes)
@@ -1309,14 +1313,14 @@ export class ComfyApp {
useTelemetry()?.trackWorkflowImported(telemetryPayload)
await useWorkflowService().afterLoadNewGraph(
workflow,
this.graph.serialize() as unknown as ComfyWorkflowJSON
this.rootGraph.serialize() as unknown as ComfyWorkflowJSON
)
requestAnimationFrame(() => {
this.graph.setDirtyCanvas(true, true)
this.canvas.setDirty(true, true)
})
}
async graphToPrompt(graph = this.graph) {
async graphToPrompt(graph = this.rootGraph) {
return graphToPrompt(graph, {
sortNodes: useSettingStore().get('Comfy.Workflow.SortNodeIdOnSave')
})
@@ -1351,12 +1355,12 @@ export class ComfyApp {
for (let i = 0; i < batchCount; i++) {
// Allow widgets to run callbacks before a prompt has been queued
// e.g. random seed before every gen
executeWidgetsCallback(this.graph.nodes, 'beforeQueued')
for (const subgraph of this.graph.subgraphs.values()) {
executeWidgetsCallback(subgraph.nodes, 'beforeQueued')
}
forEachNode(this.rootGraph, (node) => {
for (const widget of node.widgets ?? []) widget.beforeQueued?.()
})
const p = await this.graphToPrompt(this.graph)
const p = await this.graphToPrompt(this.rootGraph)
const queuedNodes = collectAllNodes(this.rootGraph)
try {
api.authToken = comfyOrgAuthToken
api.apiKey = comfyOrgApiKey ?? undefined
@@ -1397,16 +1401,7 @@ export class ComfyApp {
// Allow widgets to run callbacks after a prompt has been queued
// e.g. random seed after every gen
executeWidgetsCallback(
p.workflow.nodes
.map((n) => this.graph.getNodeById(n.id))
.filter((n) => !!n),
'afterQueued'
)
for (const subgraph of this.graph.subgraphs.values()) {
executeWidgetsCallback(subgraph.nodes, 'afterQueued')
}
executeWidgetsCallback(queuedNodes, 'afterQueued')
this.canvas.draw(true, true)
await this.ui.queue.update()
}
@@ -1481,7 +1476,7 @@ export class ComfyApp {
importA1111(this.graph, parameters)
useWorkflowService().afterLoadNewGraph(
fileName,
this.graph.serialize() as unknown as ComfyWorkflowJSON
this.rootGraph.serialize() as unknown as ComfyWorkflowJSON
)
return
}
@@ -1512,24 +1507,25 @@ export class ComfyApp {
}
const ids = Object.keys(apiData)
app.graph.clear()
app.rootGraph.clear()
for (const id of ids) {
const data = apiData[id]
const node = LiteGraph.createNode(data.class_type)
if (!node) continue
node.id = isNaN(+id) ? id : +id
node.title = data._meta?.title ?? node.title
app.graph.add(node)
app.rootGraph.add(node)
}
//TODO: Investigate repeat of for loop. Can compress?
for (const id of ids) {
const data = apiData[id]
const node = app.graph.getNodeById(id)
const node = app.rootGraph.getNodeById(id)
for (const input in data.inputs ?? {}) {
const value = data.inputs[input]
if (value instanceof Array) {
const [fromId, fromSlot] = value
const fromNode = app.graph.getNodeById(fromId)
const fromNode = app.rootGraph.getNodeById(fromId)
// @ts-expect-error fixme ts strict error
let toSlot = node.inputs?.findIndex((inp) => inp.name === input)
if (toSlot == null || toSlot === -1) {
@@ -1558,16 +1554,16 @@ export class ComfyApp {
}
}
}
app.graph.arrange()
app.rootGraph.arrange()
for (const id of ids) {
const data = apiData[id]
const node = app.graph.getNodeById(id)
const node = app.rootGraph.getNodeById(id)
for (const input in data.inputs ?? {}) {
const value = data.inputs[input]
if (value instanceof Array) {
const [fromId, fromSlot] = value
const fromNode = app.graph.getNodeById(fromId)
const fromNode = app.rootGraph.getNodeById(fromId)
// @ts-expect-error fixme ts strict error
let toSlot = node.inputs?.findIndex((inp) => inp.name === input)
if (toSlot == null || toSlot === -1) {
@@ -1597,11 +1593,11 @@ export class ComfyApp {
}
}
app.graph.arrange()
app.rootGraph.arrange()
useWorkflowService().afterLoadNewGraph(
fileName,
this.graph.serialize() as unknown as ComfyWorkflowJSON
this.rootGraph.serialize() as unknown as ComfyWorkflowJSON
)
}
@@ -1653,7 +1649,7 @@ export class ComfyApp {
this.registerNodeDef(nodeId, defs[nodeId])
}
// Refresh combo widgets in all nodes including those in subgraphs
forEachNode(this.graph, (node) => {
forEachNode(this.rootGraph, (node) => {
const def = defs[node.type]
// Allow primitive nodes to handle refresh
node.refreshComboInNode?.(defs)
@@ -1718,8 +1714,8 @@ export class ComfyApp {
// Subgraph does not properly implement `clear` and the parent class's
// (`LGraph`) `clear` breaks the subgraph structure.
if (this.graph && !this.canvas.subgraph) {
this.graph.clear()
if (this.rootGraph && !this.canvas.subgraph) {
this.rootGraph.clear()
}
}

View File

@@ -96,13 +96,13 @@ export class ChangeTracker {
const activeId = navigation.at(-1)
if (activeId) {
// Navigate to the saved subgraph
const subgraph = app.graph.subgraphs.get(activeId)
const subgraph = app.rootGraph.subgraphs.get(activeId)
if (subgraph) {
app.canvas.setGraph(subgraph)
}
} else {
// Empty navigation array means root level
app.canvas.setGraph(app.graph)
app.canvas.setGraph(app.rootGraph)
}
}
}
@@ -130,7 +130,7 @@ export class ChangeTracker {
checkState() {
if (!app.graph || this.changeCount) return
const currentState = clone(app.graph.serialize()) as ComfyWorkflowJSON
const currentState = clone(app.rootGraph.serialize()) as ComfyWorkflowJSON
if (!this.activeState) {
this.activeState = currentState
return

View File

@@ -22,7 +22,7 @@ export function clone<T>(obj: T): T {
* There are external callers to this function, so we need to keep it for now
*/
export function applyTextReplacements(app: ComfyApp, value: string): string {
return _applyTextReplacements(app.graph, value)
return _applyTextReplacements(app.rootGraph, value)
}
/** @knipIgnoreUnusedButUsedByCustomNodes */

View File

@@ -265,7 +265,7 @@ export const useLitegraphService = () => {
_initialMinSize = { width: 1, height: 1 }
constructor() {
super(app.graph, subgraph, instanceData)
super(app.rootGraph, subgraph, instanceData)
// Set up event listener for promoted widget registration
subgraph.events.addEventListener('widget-promoted', (event) => {
@@ -863,7 +863,7 @@ export const useLitegraphService = () => {
}
function goToNode(nodeId: NodeId) {
const graphNode = app.graph.getNodeById(nodeId)
const graphNode = app.canvas.graph?.getNodeById(nodeId)
if (!graphNode) return
app.canvas.animateToBounds(graphNode.boundingRect)
}
@@ -884,7 +884,9 @@ export const useLitegraphService = () => {
const canvas = canvasStore.canvas
if (!canvas) return
const bounds = createBounds(app.graph.nodes)
const nodes = canvas.graph?.nodes
if (!nodes) return
const bounds = createBounds(nodes)
if (!bounds) return
canvas.ds.fitToBounds(bounds)

View File

@@ -67,8 +67,8 @@ export const useSubgraphService = () => {
// Assertion: overriding Zod schema
for (const subgraphData of subgraphs as ExportedSubgraph[]) {
const subgraph =
comfyApp.graph.subgraphs.get(subgraphData.id) ??
comfyApp.graph.createSubgraph(subgraphData)
comfyApp.rootGraph.subgraphs.get(subgraphData.id) ??
comfyApp.rootGraph.createSubgraph(subgraphData)
registerNewSubgraph(subgraph, subgraphData)
}

View File

@@ -97,7 +97,7 @@ function executionIdToNodeLocatorId(
// It's an execution node ID
const parts = nodeIdStr.split(':')
const localNodeId = parts[parts.length - 1]
const subgraphs = getSubgraphsFromInstanceIds(app.graph, parts)
const subgraphs = getSubgraphsFromInstanceIds(app.rootGraph, parts)
if (!subgraphs) return undefined
const nodeLocatorId = createNodeLocatorId(subgraphs.at(-1)!.id, localNodeId)
return nodeLocatorId
@@ -578,10 +578,10 @@ export const useExecutionStore = defineStore('execution', () => {
* Propagates errors up subgraph chains.
*/
watch(lastNodeErrors, () => {
if (!app.graph || !app.graph.nodes) return
if (!app.rootGraph) return
// Clear all error flags
forEachNode(app.graph, (node) => {
forEachNode(app.rootGraph, (node) => {
node.has_errors = false
if (node.inputs) {
for (const slot of node.inputs) {
@@ -596,7 +596,7 @@ export const useExecutionStore = defineStore('execution', () => {
for (const [executionId, nodeError] of Object.entries(
lastNodeErrors.value
)) {
const node = getNodeByExecutionId(app.graph, executionId)
const node = getNodeByExecutionId(app.rootGraph, executionId)
if (!node) continue
node.has_errors = true
@@ -618,7 +618,10 @@ export const useExecutionStore = defineStore('execution', () => {
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)
const parentNode = getNodeByExecutionId(
app.rootGraph,
parentExecutionId
)
if (parentNode) {
parentNode.has_errors = true
}

View File

@@ -49,7 +49,7 @@ export const useSubgraphNavigationStore = defineStore(
*/
const navigationStack = computed(() =>
idStack.value
.map((id) => app.graph.subgraphs.get(id))
.map((id) => app.rootGraph.subgraphs.get(id))
.filter(isNonNullish)
)

View File

@@ -1,4 +1,3 @@
import type { LGraph } from '@/lib/litegraph/src/litegraph'
import type {
DeviceStats,
EmbeddingsResponse,
@@ -71,6 +70,6 @@ declare global {
app?: ComfyApp
/** For use by extensions and in the browser console. Where possible, import `app` and access via `app.graph` instead. */
graph?: LGraph
graph?: unknown
}
}

View File

@@ -55,7 +55,7 @@ const nodeDatas = computed(() => {
widgets
}
}
return app.graph.nodes
return app.rootGraph.nodes
.filter((node) => node.mode === 0 && node.widgets?.length)
.map(nodeToNodeData)
})

View File

@@ -61,7 +61,7 @@ export const useMissingNodes = createSharedComposable(() => {
}
const missingCoreNodes = computed<Record<string, LGraphNode[]>>(() => {
const missingNodes = collectAllNodes(app.graph, isMissingCoreNode)
const missingNodes = collectAllNodes(app.rootGraph, isMissingCoreNode)
return groupBy(missingNodes, (node) => String(node.properties?.ver || ''))
})

View File

@@ -7,7 +7,7 @@ import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import type { components } from '@/types/comfyRegistryTypes'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import { mapAllNodes } from '@/utils/graphTraversalUtil'
import { useNodePacks } from '@/workbench/extensions/manager/composables/nodePack/useNodePacks'
import type { UseNodePacksOptions } from '@/workbench/extensions/manager/types/comfyManagerTypes'
@@ -112,13 +112,9 @@ export const useWorkflowPacks = (options: UseNodePacksOptions = {}) => {
* Get the node packs for all nodes in the workflow (including subgraphs).
*/
const getWorkflowPacks = async () => {
if (!app.graph) return []
const allNodes = collectAllNodes(app.graph)
if (!allNodes.length) {
workflowPacks.value = []
return []
}
const packs = await Promise.all(allNodes.map(workflowNodeToPack))
if (!app.rootGraph) return []
const packPromises = mapAllNodes(app.rootGraph, workflowNodeToPack)
const packs = await Promise.all(packPromises)
workflowPacks.value = packs.filter((pack) => pack !== undefined)
}

View File

@@ -32,7 +32,7 @@ vi.mock('@/scripts/app', () => {
}
}),
canvas: mockCanvas,
graph: {
rootGraph: {
clear: mockGraphClear
}
}
@@ -161,7 +161,7 @@ describe('useCoreCommands', () => {
await clearCommand.function()
expect(app.clean).toHaveBeenCalled()
expect(app.graph.clear).toHaveBeenCalled()
expect(app.rootGraph.clear).toHaveBeenCalled()
expect(api.dispatchCustomEvent).toHaveBeenCalledWith('graphCleared')
})
@@ -178,7 +178,7 @@ describe('useCoreCommands', () => {
await clearCommand.function()
expect(app.clean).toHaveBeenCalled()
expect(app.graph.clear).not.toHaveBeenCalled()
expect(app.rootGraph.clear).not.toHaveBeenCalled()
// Should only remove user nodes, not input/output nodes
expect(mockSubgraph.remove).toHaveBeenCalledTimes(2)
@@ -212,7 +212,7 @@ describe('useCoreCommands', () => {
// Should not clear anything when user cancels
expect(app.clean).not.toHaveBeenCalled()
expect(app.graph.clear).not.toHaveBeenCalled()
expect(app.rootGraph.clear).not.toHaveBeenCalled()
expect(api.dispatchCustomEvent).not.toHaveBeenCalled()
})
})

View File

@@ -1,8 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { app } from '@/scripts/app'
import type { LGraphNode, LGraph } from '@/lib/litegraph/src/litegraph'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { collectAllNodes } from '@/utils/graphTraversalUtil'
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
@@ -40,12 +39,10 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
}))
}))
const mockApp: { rootGraph?: Partial<LGraph> } = vi.hoisted(() => ({}))
vi.mock('@/scripts/app', () => ({
app: {
graph: {
nodes: []
}
}
app: mockApp
}))
vi.mock('@/utils/graphTraversalUtil', () => ({
@@ -107,9 +104,8 @@ describe('useMissingNodes', () => {
nodeDefsByName: {}
})
// Reset app.graph.nodes
// @ts-expect-error - app.graph.nodes is readonly, but we need to modify it for testing.
app.graph.nodes = []
// Reset app.rootGraph.nodes
mockApp.rootGraph = { nodes: [] }
// Default mock for collectAllNodes - returns empty array
mockCollectAllNodes.mockReturnValue([])
@@ -306,13 +302,7 @@ describe('useMissingNodes', () => {
})
describe('missing core nodes detection', () => {
const createMockNode = (
type: string,
packId?: string,
version?: string
): LGraphNode =>
// @ts-expect-error - Creating a partial mock of LGraphNode for testing.
// We only need specific properties for our tests, not the full LGraphNode interface.
const createMockNode = (type: string, packId?: string, version?: string) =>
({
type,
properties: { cnr_id: packId, ver: version },
@@ -325,7 +315,7 @@ describe('useMissingNodes', () => {
mode: 0,
inputs: [],
outputs: []
})
}) as unknown as LGraphNode
it('identifies missing core nodes not in nodeDefStore', () => {
const coreNode1 = createMockNode('CoreNode1', 'comfy-core', '1.2.0')
@@ -467,8 +457,7 @@ describe('useMissingNodes', () => {
it('calls collectAllNodes with the app graph and filter function', () => {
const mockGraph = { nodes: [], subgraphs: new Map() }
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockGraph
mockApp.rootGraph = mockGraph
const { missingCoreNodes } = useMissingNodes()
// Access the computed to trigger the function
@@ -490,8 +479,7 @@ describe('useMissingNodes', () => {
it('filter function correctly identifies missing core nodes', () => {
const mockGraph = { nodes: [], subgraphs: new Map() }
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockGraph
mockApp.rootGraph = mockGraph
mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {
@@ -579,14 +567,13 @@ describe('useMissingNodes', () => {
subgraph: mockSubgraph,
type: 'SubgraphContainer',
properties: { cnr_id: 'custom-pack' }
}
} as unknown as LGraphNode
const mockMainGraph = {
nodes: [mainMissingNode, mockSubgraphNode]
}
} as Partial<LGraph> as LGraph
// @ts-expect-error - Mocking app.graph for testing
app.graph = mockMainGraph
mockApp.rootGraph = mockMainGraph
mockUseNodeDefStore.mockReturnValue({
nodeDefsByName: {

View File

@@ -5,15 +5,19 @@ import { createTestingPinia } from '@pinia/testing'
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type {
LGraph,
LGraphNode,
SubgraphNode
} from '@/lib/litegraph/src/litegraph'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
const mockApp: { rootGraph?: Partial<LGraph> } = vi.hoisted(() => ({}))
// Mock dependencies
vi.mock('@/scripts/app', () => ({
app: {
graph: null as any
}
app: mockApp
}))
vi.mock('@/utils/graphTraversalUtil', () => ({
@@ -55,17 +59,12 @@ vi.mock('@/i18n', () => ({
describe('NodeHeader - Subgraph Functionality', () => {
// Helper to setup common mocks
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
const { app } = await import('@/scripts/app')
if (hasGraph) {
;(app as any).graph = { rootGraph: {} }
} else {
;(app as any).graph = null
}
if (hasGraph) mockApp.rootGraph = {}
else mockApp.rootGraph = undefined
vi.mocked(getNodeByLocatorId).mockReturnValue({
isSubgraphNode: () => isSubgraph
} as any)
isSubgraphNode: (): this is SubgraphNode => isSubgraph
} as LGraphNode)
}
beforeEach(() => {

View File

@@ -38,9 +38,9 @@ vi.mock('@/composables/node/useNodeProgressText', () => ({
// Mock the app import with proper implementation
vi.mock('@/scripts/app', () => ({
app: {
graph: {
rootGraph: {
getNodeById: vi.fn(),
_nodes: [] // Add _nodes array for workflowStore iteration
nodes: [] // Add nodes array for workflowStore iteration
},
revokePreviews: vi.fn(),
nodePreviewImages: {}
@@ -66,7 +66,7 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
// Mock subgraph structure
const mockSubgraph = {
id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
_nodes: []
nodes: []
}
const mockNode = {
@@ -75,8 +75,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
subgraph: mockSubgraph
} as any
// Mock app.graph.getNodeById to return the mock node
vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode)
// Mock app.rootGraph.getNodeById to return the mock node
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode)
const result = store.executionIdToNodeLocatorId('123:456')
@@ -98,8 +98,8 @@ describe('useExecutionStore - NodeLocatorId conversions', () => {
})
it('should return undefined when conversion fails', () => {
// Mock app.graph.getNodeById to return null (node not found)
vi.mocked(app.graph.getNodeById).mockReturnValue(null)
// Mock app.rootGraph.getNodeById to return null (node not found)
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(null)
expect(store.executionIdToNodeLocatorId('999:456')).toBe(undefined)
})
@@ -171,7 +171,8 @@ describe('useExecutionStore - Node Error Lookups', () => {
const subgraphUuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
const mockSubgraph = {
id: subgraphUuid,
_nodes: []
getNodeById: vi.fn(),
nodes: []
}
const mockNode = {
@@ -180,7 +181,7 @@ describe('useExecutionStore - Node Error Lookups', () => {
subgraph: mockSubgraph
} as any
vi.mocked(app.graph.getNodeById).mockReturnValue(mockNode)
vi.mocked(app.rootGraph.getNodeById).mockReturnValue(mockNode)
store.lastNodeErrors = {
'123:456': {

View File

@@ -622,7 +622,7 @@ describe('useWorkflowStore', () => {
mockSubgraph.rootGraph = mockRootGraph as any
vi.mocked(comfyApp).graph = mockRootGraph as any
vi.mocked(comfyApp).rootGraph = mockRootGraph as any
vi.mocked(comfyApp.canvas).subgraph = mockSubgraph as any
store.activeSubgraph = mockSubgraph as any
})