Yep, I've been on the other end, developing such a tool, with similar findings.
Though for serialization it really needs to use a flat-ish format where each node is _at least_ on its own separate line, and sorted using some stable ordering, as otherwise there will be lots of spurious diffs in source control.
You can put IDs in the nodes and order by ID, but you still have to be a little careful in how you do that or people can add/delete the same node and create lots of changes too.
I think YAML works out pretty good as a serialization format because it doesn't create a lot of overhead unlike, say, XML which is ugly for that.
You also want to have clear standards for how you pass parameters around (some use a dictionary object passed implicitly), standards for types, good ways to do common data conversions, etc.
It's probably better when you have stronger ideas about how things should be done for a given system than as a mess of boxes trying to be a general programming language.
Yep, in my case it was a storytelling tool for visual novel games, so there wasn't much data to pass around, most nodes were either modal interactions or affecting changes upon the game world. It's also a pretty constrained domain so a lot of the bookkeeping could be done internally rather than needing to be exposed to the narrative designer. A lot of the problems you mention just didn't need to exist.
In terms of how it was implemented (C#, for context), every possible node had to be a pure object (no internal mutation) with an asynchronous "run" method. This "run" method receives the game world as a parameter (Basically just a service locator. Services hold the mutable data).
At design time, we would instantiate the nodes and use reflection to display their fields to the designer, using specific widgets if the field was a pointer to another node. The design tool also kept an unique identifier for each node, and sorted by those on serialization. We used an ini-like custom format (hadn't heard of TOML at the time or would have probably gone with that) so that there was no nesting of nodes. Instead, fields that point to other nodes would just be serialized as the target's ID. When deserializing, we would have two passes, one reads the story file into an intermediate representation, so that every node is "known", and another to apply the actual properties (which can include references to out of order nodes).
But yes, as you mention, it is a system very fit for the domain, with some choices that don't make as much sense generally. Which is part of why I see that project as successful, and kind of demonstrates how much work goes into making a good visual scripting tool.
I agree with that, a few specialized types that are understood by all your nodes can be really helpful in a more limited domain. And it's definitely better suited to relatively simple things where it gives you a sort of high-level flowchart and the nitty-gritty details can happen within the nodes themselves.
Otherwise you get a lot of low level logic of type conversion or other small details instead of a very high level view of the logic. It also helps if you can box up existing flows into a single black box as it were, since that can help you reuse code.
If you do have complex nodes that let you make it into a simplified flow chart of what the application is doing and that allows you to look inside the nodes which might have a more complex, specialized configuration, those end up being a lot nicer to program in than in something that's trying to be a general programming language with lots of low-level nodes dealing with trivial things like data conversion.
Though for serialization it really needs to use a flat-ish format where each node is _at least_ on its own separate line, and sorted using some stable ordering, as otherwise there will be lots of spurious diffs in source control.