When dynamically adding input slots to a node via JavaScript (this.addInput()), the new slots are added to the node's data model but do not render visually until the page is refreshed.
This affects all custom nodes that use dynamic inputs, including:
- cozy_ex_dynamic
- Any node using
addInput()inonConnectionsChange
Located in: ComfyUI_frontend/src/composables/graph/useGraphNodeManager.ts
The Vue rendering layer listens for node:slot-links:changed events to refresh node slot visuals:
'node:slot-links:changed': (slotLinksEvent) => {
if (slotLinksEvent.slotType === NodeSlotType.INPUT) {
refreshNodeSlots(String(slotLinksEvent.nodeId))
}
}However, this event is only fired for widget-linked inputs. In LGraphNode.ts, every trigger is gated:
if (targetInput.widget) {
graph.trigger('node:slot-links:changed', { ... })
}Regular socket inputs (without widgets) never trigger this event, so Vue never refreshes.
Manually trigger the event after modifying inputs:
// In onConnectionsChange handler, after addInput/removeInput:
if (this.graph?.trigger) {
this.graph.trigger('node:slot-links:changed', {
nodeId: this.id,
slotType: 1, // INPUT
slotIndex: slot_idx,
connected: isConnect,
linkId: link_info?.id ?? -1
});
}- ComfyUI frontend package 1.33.10+
- Likely introduced with Vue nodes merge (September 2025)
The fix should be in ComfyUI_frontend/src/lib/litegraph/src/LGraphNode.ts:
Remove the if (targetInput.widget) guard around node:slot-links:changed triggers, or add a separate event for non-widget slot changes.
Issue should be filed at: https://github.com/Comfy-Org/ComfyUI_frontend/issues
src/lib/litegraph/src/LGraphNode.ts- Lines 2863-2871, 3028-3036, 3073-3080src/composables/graph/useGraphNodeManager.ts- Lines 580-583
After the workaround above, the new input slot renders but the socket dot color doesn't update to show connected vs disconnected state visually.
In NodeSlots.vue, the InputSlot component is rendered without a :connected prop:
<InputSlot
:slot-data="input"
:node-type="..."
:node-id="..."
:index="..."
<!-- missing :connected="input.link !== null" -->
/>InputSlot.vue expects a connected prop (line 48) and applies lg-slot--connected class based on it (line 122), but:
- The prop is never passed from parent
- No CSS rules exist for
lg-slot--connectedanyway - The slot dot color is determined by type, not connection state
The Vue slot rendering doesn't implement visual connected/disconnected state. The connected prop exists but is unused/incomplete. This might be intentional (simplified design) or an oversight.
The old canvas-based LiteGraph rendering had hollow vs filled dots for connection state, but the Vue layer doesn't replicate this.
When connecting a wire to a dynamic input slot, the slot's type property is updated (e.g., from * to STRING), but the color doesn't update until page reload.
Located in: ComfyUI_frontend/src/composables/graph/useGraphNodeManager.ts
The refreshNodeSlots function (lines 149-178) does a shallow spread of the inputs array:
vueNodeData.set(nodeId, {
...currentData,
inputs: nodeRef.inputs ? [...nodeRef.inputs] : undefined,
// ...
})This creates a new array, but the slot object references remain the same. When we mutate node_slot.type = "STRING", Vue doesn't detect the change because the object reference is unchanged.
After changing the slot type, replace the slot object with a fresh copy to force Vue reactivity:
// After changing type
node_slot.type = parent_link.type;
node_slot.name = `${_PREFIX}_`;
// Force Vue reactivity by replacing slot object reference
this.inputs[slot_idx] = { ...node_slot };Then trigger the refresh event as usual. Vue will now see a new object reference and recalculate the slotColor computed property.
onConnectionsChange = function(slotType, slot_idx, event, link_info, node_slot) {
// ... handle connection logic ...
if (isConnect && parent_link) {
node_slot.type = parent_link.type;
// Replace object to trigger Vue reactivity
this.inputs[slot_idx] = { ...node_slot };
}
// Trigger Vue refresh
this.graph?.trigger('node:slot-links:changed', {
nodeId: this.id,
slotType: 1,
slotIndex: slot_idx,
connected: isConnect,
linkId: link_info?.id ?? -1
});
}The Vue frontend ignores LiteGraph's color_on/color_off slot properties.
Slot colors are determined by CSS variables based on type name:
// src/constants/slotColors.ts
export function getSlotColor(type?: string | number | null): string {
if (!type) return '#AAA'
const typeStr = String(type).toUpperCase()
return `var(--color-datatype-${typeStr}, #AAA)`
}Palette colors are defined in src/assets/palettes/dark.json under node_slot.
Inject CSS to define custom type colors:
const style = document.createElement('style');
style.textContent = `
:root {
--color-datatype-\\*: #888;
--color-datatype-STRING: #8f8;
--color-datatype-MYCUSTOMTYPE: #f80;
}
`;
document.head.appendChild(style);Note: The * character must be escaped as \\* in CSS.
When disconnecting a wire from a dynamic input, the slot is removed and a new empty slot is added. The new slot has type *, but the color stays the same as the previous slot (e.g., green for STRING) until page reload.
This issue does NOT affect the connect case - only disconnect.
Located in: ComfyUI_frontend/src/renderer/extensions/vueNodes/components/NodeSlots.vue
The InputSlot components use index-based keys:
<InputSlot
v-for="(input, index) in filteredInputs"
:key="`input-${index}`"
:slot-data="input"
...
/>When a slot is removed and a new one is added at the same index, Vue sees the same key (input-0) and reuses the existing component instance. The computed slotColor property caches the previous slot's type value and doesn't re-evaluate.
Connect case: We mutate an existing slot's type property AND replace the slot object. Vue sees the new object reference at the same index, and since it's a prop change to an existing component, the computed re-evaluates.
Disconnect case: We remove the old slot and add a completely new slot. Vue sees the same index key, reuses the component, and the computed slotColor remains cached with the old type.
- ❌ Replacing slot objects with spread:
this.inputs[i] = { ...this.inputs[i] } - ❌ Replacing entire array:
this.inputs = [...this.inputs] - ❌ Triggering refresh event multiple times with delays
- ❌ Calling
setDirtyCanvas(true, true) - ❌ Adding unique IDs to slots
The fix must be in NodeSlots.vue. The key should include slot-identifying data:
<!-- Current (broken) -->
:key="`input-${index}`"
<!-- Proposed fix -->
:key="`input-${index}-${input.type}-${input.name}`"Or use a unique slot ID if one exists.
Issue should be filed at: https://github.com/Comfy-Org/ComfyUI_frontend/issues
After disconnecting, users can refresh the page to see correct colors. The functionality is not affected - only the visual color is stale.