Issue #3622387: Name the per-row buttons, which all answered to the same thing
A second follow-up on #3622387. Naming the rows in !470 (merged) and the add control in !473 (merged) left the buttons inside those rows still answering to the same thing as each other.
A screen reader navigates a form of repeated rows by pulling up its button list, and that list carries no trace of the group each button sits in. So five timers put five buttons called Remove timer in it, and a workflow of eight nodes put twenty-four called Apply, Clone node and Remove node, with nothing saying which row any of them acts on. The visible layout answers that for a sighted user; the button list answers nothing for anyone else.
ConditionTreeBuilder already answered this, and says why:
The visible label stays one word, which is right beside the row it belongs to; the accessible name carries the row, because a screen reader reads the buttons out of that context and heard N buttons all called Remove.
That was the only aria-label in the module. Four more editors carry one now: the sequence editors (timers, assignments) through a getRemoveRowTitle() beside the two naming methods already there, the Variables node's provider table, and the Complete Modeler's node and flow rows.
Why the name is written out per feature
The accessible name has to contain the visible label in every language it is read in, or a voice-control user saying what they can see misses the control. The visible label and the row name are separate catalog entries translated independently, so composing them only happens to work in English: beside a French "Supprimer le minuteur", a composed "Supprimer Minuteur 1" no longer contains it. Writing both halves together is what keeps them in step, which is also what the condition tree does.
The cost is that one French name reads stiffly, since containment wins over fluency. Worth a look if you would rather change the visible label instead.
Two things reading it back turned up
- A node's label is not required, so an author can clear it — and an empty one put the bare "Apply" straight back on every node that had none, which is the ambiguity this fixes. The machine name is the fallback, and cannot be empty. The flow rows get that free:
getNodeOptionLabel()already falls back the same way. getLabels()'s contract described'field'as "the plugin select label" when there are now two such selects and it names only each row's own.
The flow rows also name the row once, for the section and every button in it, rather than reading the endpoints twice for the same sentence.
Tests
NodeFeatureEditorTest::testEveryRowIsNamedAfterWhereItStands()gains the remove buttons: per-row names that differ, each starting with its visible label.OrchestraCmFormTest::testFormBuildsEditableStructure()asserts the node row's three buttons and the flow row's three, the latter against the section's own title.OrchestraCmFormTest::testAnUnlabelledNodeIsNamedByItsMachineName()covers the empty label, with two unlabelled nodes that must not answer to the same name.VariablesTaskTest::testTableHoldsOnlyTheNodesProviders()covers the provider table.
French for the ten new strings, each opening with the same words as the visible label it belongs to.
Checks
phpcs Drupal and DrupalPractice clean over the whole module, phpstan level 5 [OK] No errors, cspell clean on the changed files, scripts/check-translations.php exit 0, and NodeFeatureEditorTest, OrchestraCmFormTest and VariablesTaskTest green against this branch in a lean root.