Skip to content

Conversation

christian-byrne
Copy link
Contributor

@christian-byrne christian-byrne commented Sep 28, 2025

Summary

Fixes #5692 by making widget link connection status trigger on change so Vue widgets with connected links could properly switch to the disabled state when they are implicitly converted to inputs.

Changes

  • What: Added node:slot-links:changed event tracking and reactive slot data synchronization for Vue widgets
graph TD
    A[Widget Link Change] --> B[NodeInputSlot.link setter]
    B --> C{Is Widget Input?}
    C -->|Yes| D[Trigger slot-links:changed]
    C -->|No| E[End]
    D --> F[Graph Event Handler]
    F --> G[syncNodeSlotData]
    G --> H[Update Vue Reactive Data]
    H --> I[Widget Re-render]
    
    style A fill:#f9f9f9,stroke:#333,color:#000
    style I fill:#f9f9f9,stroke:#333,color:#000
Loading

Review Focus

Widget reactivity performance with frequent link changes and event handler memory management in graph operations.

┆Issue is synchronized with this Notion page by Unito

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Sep 28, 2025
Copy link

github-actions bot commented Sep 28, 2025

🎭 Playwright Test Results

⚠️ Tests passed with flaky tests

⏰ Completed at: 10/08/2025, 12:36:12 AM UTC

📈 Summary

  • Total Tests: 489
  • Passed: 456 ✅
  • Failed: 0
  • Flaky: 3 ⚠️
  • Skipped: 30 ⏭️

📊 Test Reports by Browser

  • chromium: View Report • ✅ 447 / ❌ 0 / ⚠️ 3 / ⏭️ 30
  • chromium-2x: View Report • ✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • chromium-0.5x: View Report • ✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • mobile-chrome: View Report • ✅ 6 / ❌ 0 / ⚠️ 0 / ⏭️ 0

🎉 Click on the links above to view detailed test results for each browser configuration.

@christian-byrne christian-byrne requested review from benceruleanlu and DrJKL and removed request for benceruleanlu September 28, 2025 20:38
@DrJKL DrJKL self-assigned this Sep 29, 2025
DrJKL
DrJKL previously approved these changes Sep 29, 2025
Comment on lines 69 to 75
graph.trigger('node:slot-links:changed', {
nodeId: this.node.id,
slotType: NodeSlotType.INPUT,
slotIndex: index,
connected: this._link != null,
linkId: this._link ?? null
})
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like something that we should be doing in a store, if not now, then soon. I don't like complex decisions being made in components.

nodeId: this.node.id,
slotType: NodeSlotType.INPUT,
slotIndex: index,
connected: this._link != null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
connected: this._link != null,
connected: this.isConnected,

Comment on lines 66 to 72
const index = this.node.inputs.indexOf(this)
if (index === -1) return

graph.trigger('node:slot-links:changed', {
nodeId: this.node.id,
slotType: NodeSlotType.INPUT,
slotIndex: index,
Copy link
Contributor

Choose a reason for hiding this comment

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

option:

Suggested change
const index = this.node.inputs.indexOf(this)
if (index === -1) return
graph.trigger('node:slot-links:changed', {
nodeId: this.node.id,
slotType: NodeSlotType.INPUT,
slotIndex: index,
const slotIndex = this.node.inputs.indexOf(this)
if (slotIndex === -1) return
graph.trigger('node:slot-links:changed', {
nodeId: this.node.id,
slotType: NodeSlotType.INPUT,
slotIndex,


vueNodeData.set(nodeId, {
...currentData,
inputs: node.inputs ? [...node.inputs] : undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

Not for this PR, but what if we defined inputs and outputs as being required, so that a lack of them would just be an empty array? Would that simplify some of the logic?

@DrJKL DrJKL assigned christian-byrne and unassigned DrJKL Sep 29, 2025
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Oct 6, 2025
Copy link
Contributor

@webfiltered webfiltered left a comment

Choose a reason for hiding this comment

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

I feel like this might be around 40K lines-too-many. Give or take. 😁

@christian-byrne christian-byrne force-pushed the vue-node/instrument-widget-links branch from 5ab2ac6 to e0b9095 Compare October 6, 2025 18:12
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Oct 6, 2025
Copy link

github-actions bot commented Oct 6, 2025

🎨 Storybook Build Status

Build completed successfully!

⏰ Completed at: 10/08/2025, 12:25:55 AM UTC

🔗 Links


🎉 Your Storybook is ready for review!

Copy link
Contributor

@webfiltered webfiltered left a comment

Choose a reason for hiding this comment

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

Most importantly: LGTM!

I'm still a little confused about why we're using the LGraph "trigger" system - the reason I mention that is just because it feels like we have added a few iterations of complexity and workarounds.

If we need to revisit again, maybe we should go more nuclear on the fix, and remove any possibility of decade-old design decisions haunting us any further?

Comment on lines 87 to 94
const refreshedData = extractVueNodeData(nodeRef)

vueNodeData.set(nodeId, {
...currentData,
widgets: refreshedData.widgets,
inputs: refreshedData.inputs,
outputs: refreshedData.outputs
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const refreshedData = extractVueNodeData(nodeRef)
vueNodeData.set(nodeId, {
...currentData,
widgets: refreshedData.widgets,
inputs: refreshedData.inputs,
outputs: refreshedData.outputs
})
const { widgets, inputs, outputs } = extractVueNodeData(nodeRef)
vueNodeData.set(nodeId, { ...currentData, widgets, inputs, outputs })

This is a sidegrade at best, but... I wanted to see if it would fit. After I checked, the "work" was already done, so please take / discard as you like.

const vueComponent = getComponent(widget.type) || WidgetInputText
const slotMetadata = widget.slotMetadata
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const slotMetadata = widget.slotMetadata
const { slotMetadata } = widget

nit

Copy link
Contributor

Choose a reason for hiding this comment

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

Could pull off options at the same time

@christian-byrne christian-byrne marked this pull request as draft October 7, 2025 19:09
Comment on lines +3023 to +3029
graph.trigger('node:slot-links:changed', {
nodeId: target.id,
slotType: NodeSlotType.INPUT,
slotIndex: link_info.target_slot,
connected: false,
linkId: link_info.id
})
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is the same in all three locations, how would you feel about extracting something like maybeTriggerLinkChange(...)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it would only be simplified if it were to be made a class instance property of some sort, which would start to go backwards a bit. If it's moved to a more pure helper, then it becomes even more code, I think. Don't see a way to simplify/refactor cleanly.

- Use discriminated union pattern for type-safe events without assertions
- Preserve monkey-patching/chaining for multiple system compatibility
- Convert old (action, param) format to new {type, ...} format in trigger()
- All handlers receive properly typed LGraphTriggerEvent objects
@christian-byrne
Copy link
Contributor Author

I'm still a little confused about why we're using the LGraph "trigger" system - the reason I mention that is just because it feels like we have added a few iterations of complexity and workarounds.

If we need to revisit again, maybe we should go more nuclear on the fix, and remove any possibility of decade-old design decisions haunting us any further?

I think everyone who reads this all wants to go nuclear on the fix, but it doesn't fully align with business goals to dedicate a lot of time to doing it. We will gradually replace the system with signals-based implementation.

@christian-byrne
Copy link
Contributor Author

christian-byrne commented Oct 7, 2025

Planning on making changes:

  • Properly type events to not require any extra unneccesary assertions or awkward reflection, using standard patterns
  • Add e2e tests for the disabled state on linked widgets

@christian-byrne christian-byrne added the claude-review Add to trigger a PR code review from Claude Code label Oct 7, 2025
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Comprehensive PR Review

This review is generated by Claude. It may not always be accurate, as with human reviewers. If you believe that any of the comments are invalid or incorrect, please state why for each. For others, please implement the changes in one way or another.

Review Summary

PR: fix Vue node widgets should be in disabled state if their slots are connected with a link (#5834)
Impact: 226 additions, 82 deletions across 8 files
Purpose: Implements proper reactive event system for Vue widgets to disable when their input slots are connected

Issue Distribution

  • Critical: 0
  • High: 3
  • Medium: 4
  • Low: 0

Category Breakdown

  • Architecture: 2 issues
  • Security: 1 issue
  • Performance: 2 issues
  • Code Quality: 2 issues

Key Findings

Architecture & Design

Event System Implementation: The PR introduces a new typed event system (graphTriggers.ts) for tracking slot link changes, which is a solid architectural choice. However, the implementation has some concerns:

  1. Event Triggering Scatter: The event triggering logic is duplicated across 4 locations in LGraphNode.ts, creating maintenance overhead
  2. Type Inconsistency: The event interfaces use string|number for nodeId instead of the established NodeId type

Vue Integration: The integration with Vue's reactivity system through useGraphNodeManager is well-designed, providing proper encapsulation of LiteGraph state for Vue consumption.

Security Considerations

Event Parameter Validation: The trigger method spreads event parameters directly without validation, which could potentially allow property injection. While this is internal code, defensive programming practices should still apply.

Performance Impact

Event Handler Efficiency: Two performance concerns identified:

  1. Full Re-extraction: The refreshNodeSlots function re-extracts all node data when only slot data has changed
  2. Event Frequency: Slot link change events could fire frequently during complex operations, triggering expensive Vue reactivity updates

Bundle Impact: The changes are relatively contained and shouldn't significantly impact bundle size.

Integration Points

Vue Nodes Mode: The feature properly integrates with existing Vue node infrastructure and respects the LiteGraph.vueNodesMode flag. The slot layout sync correctly handles event handler coordination between different systems.

Widget State Management: The core feature - automatically disabling Vue widgets when their slots are connected - addresses a real UX issue where users could have conflicting input sources.

Positive Observations

  • Addresses Real User Problem: Fixes issue #5692 where Vue widgets remained enabled despite being converted to inputs
  • Type Safety: Good use of TypeScript discriminated unions for event handling
  • Backwards Compatibility: Changes maintain compatibility with existing non-Vue workflow
  • Clean Event Architecture: The event system follows established patterns and uses proper TypeScript typing
  • Proper Vue Integration: Correctly integrates with Vue's reactivity system without causing memory leaks
  • Test Coverage: Includes updated test snapshots indicating proper validation

References

Next Steps

  1. Address architectural concerns around event triggering duplication (high priority)
  2. Consider performance optimizations for high-frequency event scenarios
  3. Add parameter validation to event spreading for defensive programming
  4. Consider centralizing the event triggering logic in NodeInputSlot setters
  5. Update type definitions to use consistent NodeId typing

This is a comprehensive automated review focusing on architectural implications, performance considerations, and code quality. The core functionality addresses a legitimate user experience issue and the implementation follows established patterns in the codebase.

@christian-byrne christian-byrne removed their assignment Oct 8, 2025
@christian-byrne christian-byrne marked this pull request as ready for review October 8, 2025 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:links area:vue-migration area:widgets claude-review Add to trigger a PR code review from Claude Code size:L This PR changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The widget value is still editable or not hidden when converted to input.
3 participants