Skip to content

refactor(nodes): let the product own its node families - #36

Merged
endrix merged 1 commit into
mainfrom
refactor/product-supplied-node-families
Sep 3, 2026
Merged

endrix merged 1 commit into
mainfrom
refactor/product-supplied-node-families

Conversation

@endrix

@endrix endrix commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Stacked on #35. Pairs with wfpy-ide's declaration PR — the platform stops holding these and the product starts supplying them, so they land together.

The problem

The platform coloured nodes by reading annotations named agent, tool, viewer and source, and held an accent colour and an icon for each:

--workflow-color-task-agent:      #e87d40;
--workflow-color-external-tool:   #b26dff;
--workflow-color-external-viewer: #49a6b8;
--workflow-color-external-agent:  #ff6f91;
--workflow-color-external-source: #4fa97a;

Those are one product's decorator names. The other shell's vocabulary is actor and network — it matches none of them, so every one of its nodes drew in the default graphite. A platform that knows one product's taxonomy renders every other product's graph plain, and nothing reports it: the colours simply never match.

The neutrality gate didn't catch it because it greps for product names. agent and viewer are innocent words individually; it's the set that is one product's, and no grep sees a set.

What moves, what stays

task and network stay — every dataflow graph has nodes and subgraphs. The annotation-derived families move to the profile as data: annotation, colour, icon. The class is derived (external-actor-<id>), so core names no family, and the CSS is generated the way contributed palette icons already are.

Two things the move had to keep

Order decides. A node can carry the annotation that makes it openable and the one that says what it is. A source carries both — so without a declared order it is painted as the node it is the opposite of.

A family can be narrowed by an annotation argument. A @tool(cmd="python") showed a script icon rather than a terminal; that was a hardcoded special case reading scriptInterpreterCommands. It is now two declared families sharing one annotation, split by match and told apart by id.

Dead code found on the way

Nothing has ever set cal:hasExternalMembers — declared and read, never written — so the icon it selected and the actor-node-agent class it drove could not appear. header-task-agent had no producer either. Both were keyed on the same borrowed vocabulary.

Tests

14 new for the seam, plus the existing server tests updated to declare families like a profile does. Mutation-checked:

mutation caught
declaration order ignored 3 failed
match condition ignored 2 failed
quotes not stripped from the matched value 2 failed
id ignored for the class name 1 failed
icon CSS emitted for a family without one 1 failed
unsafe family name accepted 1 failed
unsafe colour accepted 1 failed

The generated CSS reproduces the removed rules selector-for-selector and colour-for-colour; I diffed the output against what was deleted.

Gates: typecheck 5/5, neutrality 4/4, full suite green (851 tests), bundle builds.

Not in scope, but noted

viewer-mouse-listener still keys double-click on an annotation named viewer, and the create-node wizard still lists @tool / @agent by name. Same class of coupling, different seam — worth a follow-up rather than widening this one.

The platform coloured a node by reading annotations called agent, tool,
viewer and source, and held an accent and an icon for each. Those are
one product's decorator names: the other shell's vocabulary is actor and
network, so it matched none of them and drew every node in the default
graphite. A platform that knows one product's taxonomy renders every
other product's graph plain, and nothing reports it — the colours simply
never match anything.

`task` and `network` stay. Every dataflow graph has nodes and subgraphs,
and those are the platform's own concepts.

The rest move to the profile as data: the annotation that puts a node in
a family, the accent, and the icon. The class is derived from the
declaration, so core names no family, and the CSS is generated the way
the contributed palette icons already are.

Two things the move had to keep. Declaration order decides, because a
node can carry the annotation that makes it openable AND the one that
says what it is — a source carries both, and without an order it is
painted as the node it is the opposite of. And a family can be narrowed
by an annotation argument, so one annotation splits into families that
look different: a tool that runs an interpreter against a tool that runs
anything else, which was a hardcoded special case.

Removes two dead branches found on the way: nothing has ever set
`cal:hasExternalMembers`, so the icon it selected and the class it drove
could not appear, and `header-task-agent` had no producer either. Both
were keyed on the same borrowed vocabulary.
@endrix
endrix changed the base branch from feat/source-node-icon to main September 3, 2026 15:25
@endrix
endrix merged commit b6a5f31 into main Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant