Compare commits

...

6 Commits

Author SHA1 Message Date
bymyself
4f8a794382 [refactor] replace provide/inject with direct store ref access - addresses @DrJKL's architecture feedback
Replace the provide/inject pattern for execution errors with direct store access as suggested.
Components now use executionStore.currentErrorNodeId computed ref directly, eliminating
boilerplate and improving performance with a single source of truth.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-13 22:18:01 -07:00
bymyself
fed177eaf4 [refactor] convert watch to computed pattern - addresses @DrJKL's structure comment
Replaced ref + watch pattern with computed for displayTitle, providing cleaner
reactive behavior and eliminating the need for manual sync logic.
2025-09-13 21:59:09 -07:00
bymyself
93d0109ea5 [refactor] extract node type resolution to named const - addresses @DrJKL's readability concern
Extracted the multi-fallback type resolution logic into a clearly named
variable for improved readability and maintainability.
2025-09-13 21:55:41 -07:00
Christian Byrne
a3d91cd5fc Merge branch 'main' into vue-nodes/feat/skeleton-missing-nodes 2025-09-13 21:33:36 -07:00
bymyself
6669ce25c1 update vue node data after configure 2025-09-13 12:26:27 -07:00
bymyself
fe7649624a add missing node error border 2025-09-13 09:49:43 -07:00
5 changed files with 64 additions and 27 deletions

View File

@@ -47,11 +47,6 @@
:size="nodeSizes.get(nodeData.id)"
:readonly="false"
:executing="executionStore.executingNodeId === nodeData.id"
:error="
executionStore.lastExecutionError?.node_id === nodeData.id
? 'Execution error'
: null
"
:zoom-level="canvasStore.canvas?.ds?.scale || 1"
:data-node-id="nodeData.id"
@node-click="handleNodeSelect"

View File

@@ -202,10 +202,17 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
}
})
const nodeType =
node.type ||
node.constructor?.comfyClass ||
node.constructor?.title ||
node.constructor?.name ||
'Unknown'
return {
id: String(node.id),
title: node.title || 'Untitled',
type: node.type || 'Unknown',
title: typeof node.title === 'string' ? node.title : '',
type: nodeType,
mode: node.mode || 0,
selected: node.selected || false,
executing: false, // Will be updated separately based on execution state
@@ -612,7 +619,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
// Set up widget callbacks BEFORE extracting data (critical order)
setupNodeWidgetCallbacks(node)
// Extract safe data for Vue
// Extract initial data for Vue (may be incomplete during graph configure)
vueNodeData.set(id, extractVueNodeData(node))
// Set up reactive tracking state
@@ -657,7 +664,11 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
// Chain our callback with any existing onAfterGraphConfigured callback
node.onAfterGraphConfigured = useChainCallback(
node.onAfterGraphConfigured,
initializeVueNodeLayout
() => {
// Re-extract data now that configure() has populated title/slots/widgets/etc.
vueNodeData.set(id, extractVueNodeData(node))
initializeVueNodeLayout()
}
)
} else {
// Not during workflow loading - initialize layout immediately

View File

@@ -13,7 +13,7 @@
// border
'border border-solid border-sand-100 dark-theme:border-charcoal-300',
!!executing && 'border-blue-500 dark-theme:border-blue-500',
!!(error || nodeData.hasErrors) && 'border-error',
!!hasAnyError && 'border-error',
// hover
'hover:ring-7 ring-gray-500/50 dark-theme:ring-gray-500/20',
// Selected
@@ -21,7 +21,7 @@
!!isSelected && 'outline-black dark-theme:outline-white',
!!(isSelected && executing) &&
'outline-blue-500 dark-theme:outline-blue-500',
!!(isSelected && (error || nodeData.hasErrors)) && 'outline-error',
!!(isSelected && hasAnyError) && 'outline-error',
{
'animate-pulse': executing,
'opacity-50': nodeData.mode === 4,
@@ -134,6 +134,7 @@ import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
import { useExecutionStore } from '@/stores/executionStore'
import { cn } from '@/utils/tailwindUtil'
import { useVueElementTracking } from '../composables/useVueNodeResizeTracking'
@@ -151,7 +152,6 @@ interface LGraphNodeProps {
readonly?: boolean
executing?: boolean
progress?: number
error?: string | null
zoomLevel?: number
}
@@ -176,6 +176,17 @@ const emit = defineEmits<{
useVueElementTracking(props.nodeData.id, 'node')
// Execution error state from store (direct ref access as suggested by @DrJKL)
const executionStore = useExecutionStore()
const hasExecutionError = computed(
() => executionStore.currentErrorNodeId === props.nodeData.id
)
// Computed error states for styling
const hasAnyError = computed(
(): boolean => hasExecutionError.value || !!props.nodeData.hasErrors
)
// Inject selection state from parent
const selectedNodeIds = inject(SelectedNodeIdsKey)
if (!selectedNodeIds) {

View File

@@ -36,7 +36,7 @@
</template>
<script setup lang="ts">
import { computed, onErrorCaptured, ref, watch } from 'vue'
import { computed, onErrorCaptured, ref } from 'vue'
import EditableText from '@/components/common/EditableText.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
@@ -74,18 +74,18 @@ const isEditing = ref(false)
const nodeInfo = computed(() => props.nodeData || props.node)
// Local state for title to provide immediate feedback
const displayTitle = ref(nodeInfo.value?.title || 'Untitled')
const EMPTY_STRING = ''
const DEFAULT_TITLE = 'Untitled'
// Watch for external changes to the node title
watch(
() => nodeInfo.value?.title,
(newTitle) => {
if (newTitle && newTitle !== displayTitle.value) {
displayTitle.value = newTitle
}
}
)
const resolveTitle = (info: LGraphNode | VueNodeData | undefined) => {
const title = (info?.title ?? EMPTY_STRING).trim()
if (title.length > 0) return title
const type = (info?.type ?? EMPTY_STRING).trim()
return type.length > 0 ? type : DEFAULT_TITLE
}
// Computed title that provides reactive updates
const displayTitle = computed(() => resolveTitle(nodeInfo.value))
// Event handlers
const handleCollapse = () => {
@@ -109,7 +109,5 @@ const handleTitleEdit = (newTitle: string) => {
const handleTitleCancel = () => {
isEditing.value = false
// Reset displayTitle to the current node title
displayTitle.value = nodeInfo.value?.title || 'Untitled'
}
</script>

View File

@@ -407,6 +407,20 @@ export const useExecutionStore = defineStore('execution', () => {
return executionId
}
const lastExecutionErrorNodeLocatorId = computed(() => {
const err = lastExecutionError.value
if (!err) return null
return executionIdToNodeLocatorId(String(err.node_id))
})
// Computed ref for current error node ID - components can use this directly
const currentErrorNodeId = computed<string | null>(() => {
const locator = lastExecutionErrorNodeLocatorId.value
if (!locator) return null
const localId = workflowStore.nodeLocatorIdToNodeId(locator)
return localId != null ? String(localId) : null
})
return {
isIdle,
clientId,
@@ -426,6 +440,10 @@ export const useExecutionStore = defineStore('execution', () => {
* The error from the previous execution.
*/
lastExecutionError,
/**
* NodeLocatorId for the most recent execution error.
*/
lastExecutionErrorNodeLocatorId,
/**
* The id of the node that is currently being executed (backward compatibility)
*/
@@ -470,6 +488,10 @@ export const useExecutionStore = defineStore('execution', () => {
_executingNodeProgress,
// NodeLocatorId conversion helpers
executionIdToNodeLocatorId,
nodeLocatorIdToExecutionId
nodeLocatorIdToExecutionId,
/**
* The node ID that currently has an execution error (as string)
*/
currentErrorNodeId
}
})