Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
reactiveWidgets.splice(0, reactiveWidgets.length, ...v)
}
})
const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
Object.defineProperty(node, 'inputs', {
get() {
return reactiveInputs
},
set(v) {
reactiveInputs.splice(0, reactiveInputs.length, ...v)
Comment on lines +226 to +227
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nit, but I'd love a more descriptive parameter name.

}
})
Comment on lines +221 to +229
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

cat -n src/composables/graph/useGraphNodeManager.ts | head -250 | tail -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2020


🏁 Script executed:

cat -n src/composables/graph/useGraphNodeManager.ts | head -230 | tail -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1265


🏁 Script executed:

rg -n 'node\.inputs' --type=ts --type=vue -A 2 -B 2 | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

rg -n 'Object\.defineProperty' --type=ts --type=vue -A 5 | head -150

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 97


🏁 Script executed:

rg -n 'this\.inputs' src/lib/litegraph/ --type=ts -A 2 -B 2 | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5581


🏁 Script executed:

rg -n 'Object\.keys|Object\.entries|Object\.getOwnProperty' --type=ts src/composables/ src/components/ | grep -i node

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 281


🏁 Script executed:

rg -n 'for.*in.*node|for.*in.*this' --type=ts src/lib/litegraph/ | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 3572


🏁 Script executed:

rg -n 'Object\.defineProperty.*node.*inputs|Object\.defineProperty.*node.*widgets' --type=ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 256


🏁 Script executed:

rg -n 'node\.inputs' src/composables/ --type=ts -B 2 -A 2 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2597


🏁 Script executed:

fd useGraphNodeManager -type f

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 240


🏁 Script executed:

node << 'EOF'
const obj = {};
Object.defineProperty(obj, 'test', {
  get() { return 42; }
});
const descriptor = Object.getOwnPropertyDescriptor(obj, 'test');
console.log('Default descriptor:', descriptor);
console.log('Can iterate with for...of:', [...Object.keys(obj), 'N/A']);
// Test for...of on array with property
const arr = [1, 2, 3];
Object.defineProperty(arr, 'custom', { get() { return 99; } });
for (const item of arr) {
  console.log('for...of item:', item);
}
console.log('custom property in for...of:', 'custom' in arr ? arr.custom : 'not iterated');
EOF

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 298


🏁 Script executed:

rg -n 'shallowReactive' src/composables/graph/ --type=ts -B 2 -A 8 | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2476


🏁 Script executed:

find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "useGraphNodeManager\|extractVueNodeData" 2>/dev/null

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 682


🏁 Script executed:

rg -n 'Object\.defineProperty' src/ --type=ts -B 3 -A 3 | head -60

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 5533


🏁 Script executed:

rg -n 'node\.inputs\s*=' --type=ts src/composables/ src/components/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

rg -n 'node\.widgets\s*=' --type=ts src/composables/ src/components/

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

grep -r "configurable\|enumerable" src/composables/ src/components/ --include="*.ts" 2>/dev/null | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 207


The reactive properties for inputs and widgets are correctly implemented without explicit descriptor flags. While adding configurable: true and enumerable: true would be defensive best-practice for future-proofing (similar to patterns in Storybook code), the current implementation poses no actual compatibility issues:

  • for...of iteration (used throughout the codebase) works on arrays regardless of enumerable flag
  • No code attempts to reassign node.inputs or node.widgets after initial setup
  • Default flags (enumerable: false, configurable: false) do not prevent the current usage patterns

Adding descriptor flags is optional, but if you want to follow defensive coding practices, apply this diff:

 const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
 Object.defineProperty(node, 'inputs', {
+  configurable: true,
+  enumerable: true,
   get() {
     return reactiveInputs
   },
   set(v) {
     reactiveInputs.splice(0, reactiveInputs.length, ...v)
   }
 })

And similarly for widgets at lines 212–220.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
Object.defineProperty(node, 'inputs', {
get() {
return reactiveInputs
},
set(v) {
reactiveInputs.splice(0, reactiveInputs.length, ...v)
}
})
const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
Object.defineProperty(node, 'inputs', {
configurable: true,
enumerable: true,
get() {
return reactiveInputs
},
set(v) {
reactiveInputs.splice(0, reactiveInputs.length, ...v)
}
})
🤖 Prompt for AI Agents
In src/composables/graph/useGraphNodeManager.ts around lines 212–220 (widgets)
and 221–229 (inputs), the Object.defineProperty calls omit descriptor flags;
update both property descriptors to include configurable: true and enumerable:
true so the properties are defensively reusable—i.e., augment the existing
defineProperty options for node.widgets and node.inputs to explicitly set
configurable: true and enumerable: true while keeping the same getter and setter
logic.


const safeWidgets = reactiveComputed<SafeWidgetData[]>(() => {
node.inputs?.forEach((input, index) => {
Expand Down Expand Up @@ -252,7 +261,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
badges,
hasErrors: !!node.has_errors,
widgets: safeWidgets,
inputs: node.inputs ? [...node.inputs] : undefined,
inputs: reactiveInputs,
outputs: node.outputs ? [...node.outputs] : undefined,
Comment on lines +264 to 265
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get context around lines 264-265 in the target file
echo "=== File context around lines 264-265 ==="
wc -l src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Read the relevant section of the file
sed -n '240,290p' src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1502


🏁 Script executed:

#!/bin/bash
# Check if outputs are mutated dynamically like inputs
echo "=== Checking for node.outputs mutations ==="
rg -n -C3 --type=ts --type=vue 'node\.outputs\s*=|\.outputs\s*\[.*\]\s*=' || echo "No direct mutations found"

echo -e "\n=== Checking for outputs array mutations (push/pop/splice/etc) ==="
rg -n -C3 --type=ts --type=vue '\.outputs\.(push|pop|splice|shift|unshift)' || echo "No array method mutations found"

echo -e "\n=== Checking for outputs property access/mutation ==="
rg -n --type=ts --type=vue 'outputs' src/composables/graph/useGraphNodeManager.ts | head -20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 395


🏁 Script executed:

#!/bin/bash
# Find where reactiveInputs is created in the file
sed -n '200,270p' src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2316


🏁 Script executed:

#!/bin/bash
# Search for reactiveInputs definition in the entire file
rg -n 'reactiveInputs' src/composables/graph/useGraphNodeManager.ts -B5 -A5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Check broader usage of outputs in the codebase to see if it's modified anywhere
rg -n 'outputs' src/composables/graph/ --type=ts -A2 -B2 | head -80

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1312


🏁 Script executed:

#!/bin/bash
# Search across entire src directory for outputs mutations/modifications
echo "=== Searching for outputs property assignments ==="
rg '\bnode\.outputs\s*=' src/ --type=ts -A2 -B2 || echo "No node.outputs assignments"

echo -e "\n=== Searching for outputs in returned data being modified ==="
rg 'vueNode\.outputs|nodeData\.outputs' src/ --type=ts -A2 -B2 | head -40

echo -e "\n=== Check if outputs are used in reactiveComputed like inputs ==="
rg 'node\.outputs' src/composables/graph/useGraphNodeManager.ts -B2 -A2

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1577


🏁 Script executed:

#!/bin/bash
# Check how VueNodeData outputs are used in components
echo "=== Search for outputs usage in Vue components ==="
rg 'outputs' src/components/ --type=ts -l | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Get the exact pattern used for reactiveInputs to verify the suggested diff
sed -n '221,229p' src/composables/graph/useGraphNodeManager.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 338


🏁 Script executed:

#!/bin/bash
# Check for any comments or TODOs about outputs handling
rg -n 'outputs|TODO|FIXME' src/composables/graph/useGraphNodeManager.ts | grep -i 'output' | head -10

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 237


🏁 Script executed:

#!/bin/bash
# Verify the VueNodeData type definition to see outputs type
rg -n 'interface VueNodeData|type VueNodeData' src/composables/graph/useGraphNodeManager.ts -A20

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 584


Apply the same reactive pattern to outputs for consistency with inputs.

Outputs are currently returned as a shallow copy ([...node.outputs]) while inputs use a shallowReactive wrapper with property interception. This creates an inconsistency in reactivity tracking. Additionally, outputs are mutated dynamically in the codebase (e.g., in src/utils/linkFixer.ts), so they should be reactive to ensure proper Vue tracking.

Apply the same reactive pattern used for inputs:

 const reactiveInputs = shallowReactive<INodeInputSlot[]>(node.inputs ?? [])
 Object.defineProperty(node, 'inputs', {
   get() {
     return reactiveInputs
   },
   set(v) {
     reactiveInputs.splice(0, reactiveInputs.length, ...v)
   }
 })
+const reactiveOutputs = shallowReactive<INodeOutputSlot[]>(node.outputs ?? [])
+Object.defineProperty(node, 'outputs', {
+  get() {
+    return reactiveOutputs
+  },
+  set(v) {
+    reactiveOutputs.splice(0, reactiveOutputs.length, ...v)
+  }
+})

Update line 265:

-  outputs: node.outputs ? [...node.outputs] : undefined,
+  outputs: reactiveOutputs,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inputs: reactiveInputs,
outputs: node.outputs ? [...node.outputs] : undefined,
inputs: reactiveInputs,
outputs: reactiveOutputs,
🤖 Prompt for AI Agents
In src/composables/graph/useGraphNodeManager.ts around lines 264-265, outputs
are returned as a plain shallow copy while inputs use a shallowReactive wrapper
with property interception; change the outputs handling to mirror inputs by
creating a shallow copy of node.outputs and wrapping it with shallowReactive
plus the same property interception logic used for reactiveInputs so mutations
to the outputs array are tracked by Vue (replace the [...node.outputs] return
with the same pattern used for inputs: copy the array, wrap it in
shallowReactive, and apply the property's getter/setter interception).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai this would be good to do on its own as a dedicated follow-up PR to keep this one focused.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DrJKL, that makes sense! Keeping this PR focused on inputs is a good call.

Would you like me to create a GitHub issue to track the outputs reactivity work as a follow-up? I can include the context from this discussion and the suggested implementation approach.


🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Utilize ref and reactive for reactive state

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7137
File: src/components/rightSidePanel/parameters/TabParameters.vue:10-0
Timestamp: 2025-12-04T21:43:49.363Z
Learning: Vue 3.5+ supports reactive props destructure in <script setup>. Destructuring props directly (e.g., `const { nodes } = defineProps<{ nodes: LGraphNode[] }>()`) maintains reactivity through compiler transformation. This is the recommended modern approach and does not require using `props.x` or `toRef`/`toRefs`.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/lib/litegraph/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:56.371Z
Learning: Applies to src/lib/litegraph/**/*.{js,ts,jsx,tsx} : Do not replace `&&=` or `||=` with `=` when there is no reason to do so. If you do find a reason to remove either `&&=` or `||=`, leave a comment explaining why the removal occurred

Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T20:22:23.620Z
Learning: Applies to **/*.vue : Use `ref` for reactive state in Vue Composition API components

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Use ref/reactive for state management in Vue 3 Composition API

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue InputSwitch component with ToggleSwitch

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T20:22:23.620Z
Learning: Applies to **/*.vue : Use Vue 3.5 TypeScript style for default prop declaration with reactive props destructuring; do not use `withDefaults` or runtime props declaration

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and assign it to @AustinMroz

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai make that issue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for outputs reactivity follow-up

gh issue create \
  --title "Make node outputs reactive in Vue" \
  --body "## Context

Follow-up from PR #7546 which made node inputs reactive.

Currently, outputs are returned as a shallow copy (\`[...node.outputs]\`) while inputs use a \`shallowReactive\` wrapper with property interception. This creates an inconsistency in reactivity tracking.

## What needs to be done

Apply the same reactive pattern to outputs that was implemented for inputs in PR #7546:

1. Create \`reactiveOutputs\` using \`shallowReactive<INodeOutputSlot[]>(node.outputs ?? [])\`
2. Define a property on the node with getter/setter that intercepts mutations
3. Update the VueNodeData return to use \`reactiveOutputs\` instead of the shallow copy

This ensures mutations to the outputs array are properly tracked by Vue, maintaining consistency with how inputs are handled.

## Related

- PR: https://github.com/Comfy-Org/ComfyUI_frontend/pull/7546
- Discussion: https://github.com/Comfy-Org/ComfyUI_frontend/pull/7546#discussion_r2621739275
- File: \`src/composables/graph/useGraphNodeManager.ts\`
- Requested by: @DrJKL" \
  --assignee "AustinMroz"

Length of output: 57


@DrJKL, I've created issue #7547 for making node outputs reactive and assigned it to @AustinMroz.

🔗 #7547

The issue includes the technical context about applying the same shallowReactive pattern used for inputs, along with backlinks to this PR and discussion.


🧠 Learnings used
Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-24T19:47:02.860Z
Learning: Applies to src/**/*.vue : Utilize ref and reactive for reactive state

Learnt from: DrJKL
Repo: Comfy-Org/ComfyUI_frontend PR: 7137
File: src/components/rightSidePanel/parameters/TabParameters.vue:10-0
Timestamp: 2025-12-04T21:43:49.363Z
Learning: Vue 3.5+ supports reactive props destructure in <script setup>. Destructuring props directly (e.g., `const { nodes } = defineProps<{ nodes: LGraphNode[] }>()`) maintains reactivity through compiler transformation. This is the recommended modern approach and does not require using `props.x` or `toRef`/`toRefs`.

Learnt from: simula-r
Repo: Comfy-Org/ComfyUI_frontend PR: 7252
File: src/renderer/extensions/vueNodes/components/ImagePreview.vue:151-158
Timestamp: 2025-12-11T03:55:57.926Z
Learning: In src/renderer/extensions/vueNodes/components/ImagePreview.vue and LGraphNode.vue, keyboard navigation for image galleries should respond to node-level focus (via keyEvent injection from LGraphNode), not require focus within the image preview wrapper itself. This allows users to navigate the gallery with arrow keys immediately when the node is focused/selected.

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T20:22:23.620Z
Learning: Applies to **/*.vue : Use `ref` for reactive state in Vue Composition API components

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Use ref/reactive for state management in Vue 3 Composition API

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: src/components/CLAUDE.md:0-0
Timestamp: 2025-11-24T19:47:45.616Z
Learning: Applies to src/components/**/*.vue : Replace PrimeVue InputSwitch component with ToggleSwitch

Learnt from: CR
Repo: Comfy-Org/ComfyUI_frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-09T20:22:23.620Z
Learning: Applies to **/*.vue : Use Vue 3.5 TypeScript style for default prop declaration with reactive props destructuring; do not use `withDefaults` or runtime props declaration

flags: node.flags ? { ...node.flags } : undefined,
color: node.color || undefined,
Expand Down