Skip to content

Conversation

AustinMroz
Copy link
Collaborator

@AustinMroz AustinMroz commented Sep 20, 2025

The parentId property on links and reroutes was not handled at all in the "Convert to Subgraph" code.
This needs to be addressed in 4 cases

  • A new external input link must have parentId set to the first non-migrated reroute
  • A new external output link must have the parentId of it's eldest remaining child set to undefined
  • A new internal input link must have the parentId of it's eldest remaining child set to undefined
  • A new internal output link must have the parentId set to the first migrated reroute

This is handled in two parts by adding logic where the boundry links is created

  • The change involves mutation of inputs (which isn't great) but the function here was already mutating inputs into an invalid state
    • @DrJKL Do you see a quick way to better fix both these cases?

Looks like litegraph tests aren't enabled and cursory glance shows multiple need to be updated to reflect recent changes. I'll still try to add some tests anyways.
EDIT: Tests are non functional. Seems the subgraph conversion call requires the rest of the frontend is running and has event listeners to register the subgraph node def. More work than anticipated, best revisited later

Resolves #5669

┆Issue is synchronized with this Notion page by Unito

@AustinMroz AustinMroz requested a review from a team as a code owner September 20, 2025 21:26
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Sep 20, 2025
Copy link

github-actions bot commented Sep 20, 2025

🎭 Playwright Test Results

All tests passed!

⏰ Completed at: 09/22/2025, 05:16:53 PM UTC

📈 Summary

  • Total Tests: 460
  • Passed: 431 ✅
  • Failed: 0
  • Flaky: 0
  • Skipped: 29 ⏭️

📊 Test Reports by Browser

  • chromium: View Report • ✅ 424 / ❌ 0 / ⚠️ 0 / ⏭️ 29
  • chromium-2x: View Report • ✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • chromium-0.5x: View Report • ✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0
  • mobile-chrome: View Report • ✅ 4 / ❌ 0 / ⚠️ 0 / ⏭️ 0

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

@DrJKL
Copy link
Contributor

DrJKL commented Sep 21, 2025

Often, the resolution to mutating inputs is to introduce an intermediate pure function that returns the new desired state.
So for this case...

  1. Take in the link to split
  2. Generate the new links
  3. Return the set of new links that direct
    • To the Subgraph externally
    • From the Subgraph Input internally
  4. Swap out the original with the replacements

Right?

Another benefit of devolving the structure like this is that the logic can be tested in isolation and then composed.

I feel like there's a way to handle reroutes as links under the hood, but I'd have to look into it further to really check.

@AustinMroz
Copy link
Collaborator Author

Appreciate the insight.

I feel like there's a way to handle reroutes as links under the hood, but I'd have to look into it further to really check.

My understanding is that it was an explicit design goal for the new reroutes to be transparent to the linking logic. It's always safe to pretend they don't exist unless the code you're working with specifically involves reroutes. But that's more a question for @webfiltered, IIRC.

@webfiltered
Copy link
Contributor

My understanding is that it was an explicit design goal for the new reroutes to be transparent to the linking logic. It's always safe to pretend they don't exist unless the code you're working with specifically involves reroutes. But that's more a question for @webfiltered, IIRC.

Definitely by design. Reroutes are not links; the original code treating them as links was the cause of extreme misery and woe.

They are virtual waypoints, and the new system treats them as such.

Definitely room to improve the reroute code though; it had legacy constraints.

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.

Off the top of my head, this looks like it will be corrupt if you convert to subgraph when using "part" of a reroute chain.

Scenarios I wanted to address a while back:

  1. A node with an input link that goes outside the subgraph + the "second" reroute only
    The closest reroute is NOT selected
  2. A node with an output link that goes outside, taking the first, third, and fifth reroutes
    Skip the second and fourth reroutes
  3. Two connected nodes, but only some / none of their reroutes
    • v1: Just bring ALL the reroutes in regardless
  4. Pick only a reroute - not the node before or the node after
    • v1: Stupid extreme edge case, just ignore it and pretend you didn't see it

I am aware this is awful.

Comment on lines 280 to 289
let child: SerialisableLLink | Reroute = linkData
let nextReroute = reroutes.get(child.parentId ?? 0)
while (child.parentId && nextReroute) {
child = nextReroute
nextReroute = reroutes.get(child.parentId ?? 0)
}
//set outside link to first remaining reroute OR undefined
link.parentId = child.parentId
//ensure end of chain is undefined
child.parentId = undefined
Copy link
Contributor

Choose a reason for hiding this comment

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

undefined is the idiomatic JS for "not set" / "none" - 0 is actually a valid reroute ID.

The use of 0 as a special value in LiteGraph is not something you see in libraries (for good reason - it allows you to use truthy checks, but breaks numeric checks). Even base JS uses -1 in its always-numeric built-ins.

Copy link
Contributor

Choose a reason for hiding this comment

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

(Also thanks! Decided to make sure I wasn't losing my mind, and found a bad refactor of mine from Feb.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IT IS? lastRerouteId starts at 0, is incremented before use, and there's existing code sections that depend on valid rerouteIds always being truthy

src/LLink.ts
208:    if (!linkSegment.parentId) return []
232:    if (!linkSegment.parentId) return
501:    if (this.parentId) copy.parentId = this.parentId

This is one of the cases where js will happily let you index by undefined for correct behaviour, and I'm just hoping to satisfy typescript. It can be easily changed to a ternary if it feels ick to you.

Off the top of my head, this looks like it will be corrupt if you convert to subgraph when using "part" of a reroute chain.

Ran into a couple of these when trying to write test cases here. Yeah... it gets awful fast. If I'm following along right, 'ghost' reroutes (with no links) are not intended. A reroute that isn't linked through a parentId can be selected, but isn't drawn. There's some actions which can cause them, like a "Right click slot -> Disconnect Links".

Worthyness of implementation time aside, I'd expect a robust implementation to be a single rule that handles all these cases. Automatic inclusion seems both more UX friendly and seems to have the easier implementation of the 2.

  1. Walk the chain of parentIds for every link and reroute, only append chain if it reaches a selected item
  2. Assign each reroute a (stable) depth from the reversed parentId list. Sort and rebuild parentId chain from depth for each reroute

Worth editing to the unpacking code to console.error; continue'when a chain has an unexpected dead end? It shouldn't actually break anything, but feels like giving up.

Copy link
Contributor

Choose a reason for hiding this comment

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

IT IS? lastRerouteId starts at 0,

Yeah I forgot I did this - I -think- it was added in. I found it after I wrote the comment, but decided not to correct myself - there may be workflows from a couple versions that would use the old reroute ID 0 and cause unexpected behaviour.

The TL;DR is that the API design is still that ID 0 is valid - the current impl. detail is that it will not be on workflows created right now. There should be many more code paths that do nullish checks. So trying to have 0 also mean "not set" will result in -- what's the technical -- weird shit happening. 😃

(It looks like you were typing that response at the same time as I was doing my refactor - and I missed it~)

Copy link
Contributor

Choose a reason for hiding this comment

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

'ghost' reroutes (with no links) are not intended

Yep - that's nasty.

Automatic inclusion seems both more UX friendly

SGTM!

Worth editing to the unpacking code to console.error; continue'

Pretty much always worth logging unexpected state, imo. Actionable information beats silently breaking for me. Discarding error state is a pet hate of mine - it makes debugging impossible.

Spent a lot of time this year making sure LiteGraph absolutely spits the dummy in really obvious ways when things break. Throw, log, or.. do something. Initial work on subgraph had an insane difficulty spike at the start, due to this - tracing why a change seemed to do nothing just took forever (no errors, no warnings.. just.. didn't work).

Yeah... it gets awful fast.

Just QFT - sounds like a great tagline / subtitle.

@webfiltered
Copy link
Contributor

Scenarios I wanted to address a while back:

Ah lost some other words that were meant to come before this. TL;DR would have been: Reroutes were put in the too-hard basket and this limitation was acknowledged somewhere. Sorry~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:M This PR changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't unpack subgraph with a reroute inside
4 participants