feat: let a producer say how its graph reads, without teaching the platform - #37
Merged
Merged
Conversation
This was referenced Sep 3, 2026
…atform Four hooks a dataflow producer needs and could not have. Each follows the pattern `external-node-by-meta` established: the producer states a fact, the platform derives something selectable from it, and the naming happens in the product's own stylesheet. No new word enters core; the gates stay green because there is nothing product-shaped to find. The kind hook was already here and was only half wired. `<kind>-node` was emitted inside the external branch, so it worked for exactly the case the first product needed and silently did nothing for any other: a producer introducing a kind of its own got a node the platform renders happily and its own stylesheet cannot select, with nothing anywhere to say why. Nothing about the hook ever depended on externality. It is hoisted, so every node gets it. Port roles get the same treatment. `control` had a class from before roles were general, and the property panel and port views read it, so it stays; every role now also gets `port-role-<role>`. This matters more than styling: roles are the axis a reader wants to switch off. Wiring that is present on every single element carries no information until the moment it does, and without a hook there is nothing to hang that switch on. Edges gain `role` and `width`. A dataflow network carries several kinds of connection at once — one that carries tokens, one that carries a value, one that carries the thing clocking all of it — and drawn identically they can only be told apart by tracing them by hand. `undirected` lets a producer ask for the no-arrow edge type that already existed for structural wiring, for a connection with genuinely no direction to draw. Feedback is the one with a real design question in it, so it is worth being explicit about why it works this way. The derived answer is a fact about the DRAWING: which edges point backwards in the order the layout will use. That is the honest answer when nobody knows better, and it stays the default. But a producer often does know better, because in its own terms a loop has a designated closing element and the shape cannot show which one it is — several edges of one cycle are equally good candidates by shape alone, and the greedy sequencing picks among them by degree. So `meta.feedback` anywhere in a document switches that whole graph to the producer's answer. All-or-nothing, deliberately, and the test that pins it names the FORWARD edge of a two-node loop — a choice the derivation would never make, which is what proves the declaration is honoured rather than merely agreeing. Merging the two sets would mark one cycle twice, once where the producer said and once where the heuristic guessed, and a reader has no way to tell which mark carries meaning. A document that declares nothing behaves exactly as before. The derived-class helper checks its input against a class-name pattern, because the value is interpolated into a class attribute and a producer is not necessarily careful. There is a test with a space and a quote in a kind. Still outstanding from the same list, both client-side and larger: the hide-by-role toggle in the palette header, and edge labels, which the edge view has no notion of today.
endrix
force-pushed
the
feat/producer-declared-render-hooks
branch
from
September 3, 2026 18:50
b5b61b2 to
7e9d234
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four hooks a dataflow producer needs and could not have. Each follows the pattern
external-node-by-metaestablished: the producer states a fact, the platform derives something selectable from it, and the naming happens in the product's own stylesheet. No new product word enters core, and the gates stay green because there is nothing product-shaped to find.The kind hook was already here and only half wired
<kind>-nodewas emitted inside the external branch, so it worked for exactly the case the first product needed and silently did nothing for any other. A producer introducing a kind of its own got a node the platform renders happily and its own stylesheet cannot select, with nothing anywhere to say why. Nothing about the hook ever depended on externality. It is hoisted, so every node gets it.This sits alongside the node-families mechanism from #36 rather than competing with it: families are a product's declared vocabulary, this is a fallback selector derived from whatever kind arrived.
Port roles
controlhad a class from before roles were general, and the property panel and port views read it, so it stays. Every role now also getsport-role-<role>.This matters more than styling. Roles are the axis a reader wants to switch off: wiring present on every single element carries no information until the moment it does, and without a hook there is nothing to hang that switch on.
Edges gain
role,widthandundirectedA dataflow network carries several kinds of connection at once — one that carries tokens, one that carries a value, one that carries the thing clocking all of it — and drawn identically they can only be told apart by tracing them by hand.
undirectedlets a producer ask for the no-arrow edge type that already existed for structural wiring, for a connection with genuinely no direction to draw.Feedback, which has a real design question in it
The derived answer is a fact about the drawing: which edges point backwards in the order the layout will use. That is the honest answer when nobody knows better, and it stays the default.
But a producer often does know better, because in its own terms a loop has a designated closing element and the shape cannot show which one it is. Several edges of one cycle are equally good candidates by shape alone, and the greedy sequencing picks among them by degree. So
meta.feedbackanywhere in a document switches that whole graph to the producer's answer.All-or-nothing, deliberately. The test that pins it names the forward edge of a two-node loop — a choice the derivation would never make, which is what proves the declaration is honoured rather than merely agreeing. Merging the two sets would mark one cycle twice, once where the producer said and once where the heuristic guessed, and a reader has no way to tell which mark carries meaning.
Compatibility
Everything except the hoisted kind class is inert unless a producer sets one of the new keys, so a document that declares nothing behaves exactly as before. The derived-class helper checks its input against a class-name pattern, because the value is interpolated into a class attribute and a producer is not necessarily careful; there is a test with a space and a quote in a kind.
Verification
producer-declared-render-hooks.test.tsStill outstanding from the same list
Both client-side and larger: the hide-by-role toggle, and edge labels, which the edge view has no notion of today. The first consumer of this reached the same conclusion about roles from the other direction and filters them producer-side instead, which also removes the ports rather than leaving dangling pins.