Problem/Motivation
SubprocessMappingTrait exists because the two maps are the contract between a parent and a child, and its own docblock gives the reason: a contract that two classes each implement is a contract that holds only until one of them is edited, and the remote node was written by copying the local one. Two pieces of that contract are still written twice.
The input and output textareas are byte-identical in both subprocess nodes, titles and descriptions included. The sentence that teaches an author the mapping syntax is part of the syntax, so improving it on one node leaves the other teaching the old one.
So is the nested loop that refuses a reserved name on either side of either map. That refusal is the only thing standing between an author and a mapping that silently does nothing: the read finds nothing because the resolver hides the variable, and the write lands where nothing in the child can see it. Two copies is two places to keep it in step, and it had no test at all on the remote node, so it was passing there only because the loop had been pasted in.
Separately, InstanceActionLink, TaskActionLink and TaskOperations each declare the same three answers that follow from rendering an action rather than a value: an empty query(), clickSortable() returning FALSE, and cache contexts of user and url. Each carries its own comment explaining them, and the three comments have already drifted into three wordings of one rule. The module already answers this shape with a base class twice, in InstanceLinkFieldBase and LinkedUserFieldBase.
Proposed resolution
Move the two map fields and the reserved-name refusal onto SubprocessMappingTrait as buildMapFields() and validateMapNames(). Neither node gains a dependency, because both already use the trait.
Add ActionLinkFieldBase to Orchestra Views for the three action-link fields, with the reasoning written once. The dependency it needs is already declared: Orchestra Inbox Views lists Orchestra Views in its info file while importing nothing from it.
AssignmentCacheTrait stays on the concrete classes rather than moving to that base. It reads the matcher and the viewer off its host, as its own docblock requires, and a subclass is where those are held.
Remaining tasks
None.
API changes
SubprocessMappingTrait gains two protected methods. ActionLinkFieldBase is new. No existing signature changes.
Data model changes
None.
User interface changes
None. The rendered fields and the refusal message are unchanged; this moves where they are written, not what they say.
Release notes snippet
The two subprocess nodes now share the variable-mapping fields and the refusal that keeps a mapping off the engine's reserved namespace, and the three action-link Views fields share a base class, so each of those rules is written once instead of once per node type.
AI-Generated: Yes (Claude Code was used to help draft this issue summary and to write the code and tests on the merge request. I reviewed and ran the work myself before posting it.)
Issue fork orchestra-3623818
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #5
mably commented