Problem/Motivation
Two interaction plugins report a validation refusal with $form_state->setErrorByName() from inside their validateConfigurationForm(), at three call sites between them. In a subform that names the error rather than the element, and the name it builds does not reach the field it is about.
SubformState::setErrorByName() composes the error name from the subform's #array_parents, while an error is matched to its element by that element's #parents. In Orchestra the two differ, because the classic modeler renders the whole workflow under a structure wrapper and hands the plugin subform a #parents path without it. Measured on the interaction settings subform of a node in the classic modeler:
#parents: ["nodes","n_task","settings","interaction_settings"] #array_parents: ["structure","nodes","n_task","settings","interaction_settings"]
So the error is recorded under structure][nodes][n_task][settings][interaction_settings][form_operation and belongs to no element on the page. Two things follow, both silent:
- the offending field is never marked, so it gets no error styling and no
aria-invalid; OrchestraCmFormopens a collapsed node section by matching error keys against^(nodes|flows)\]\[, andstructure][does not match, so the section holding the field stays shut.
The author is shown the message with nothing on screen to correct, on a form whose sections are collapsed by default.
Each site carries a comment asserting the opposite, that the host "hands the plugin a SubformState, whose setErrorByName() prefixes the subform's own place in the form for us". That is true of what it builds and false of what the error matcher reads.
Steps to reproduce
- Author a workflow in the classic modeler with an interaction task node whose interaction is Entity form.
- Set its operation to Delete, which the plugin refuses.
- Save. The message appears; the node section stays collapsed and the select is not marked.
Proposed resolution
Set the error on the element instead of naming it, which is what the user and email audiences already do:
$form_state->setError($form['form_operation'], $message);FormState::setError() reads the element's own #parents, so the key matches however deeply the host nests the subform. The three call sites are EntityInteraction::validateConfigurationForm() (form_operation) and WebformInteraction::validateConfigurationForm() (mode and context_display). The comments above them need correcting with the fix.
Remaining tasks
- Fix all three call sites and their comments.
- Cover each with a kernel test that drives
OrchestraCmFormthrough the form builder and asserts the error key is the submitted path, since where an error lands is only observable through a real host form. - Check whether any other plugin subform names an error rather than setting it. A sweep of every
validateConfigurationForm()in the project finds no others: the node features and the condition tree build their keys from#parentsby hand, and the plain forms are not subforms.
User interface changes
None beyond the refusal reaching the field it is about: the control is marked and its section opens.
AI-Generated: Yes (Claude Code was used to help draft this issue summary. The behavior described was measured by building the classic modeler form and reading the two parent paths off the interaction settings subform.)
Issue fork orchestra-3624283
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 #4
mably commentedComment #6
mably commented