Issue #3622387: Name every repeated row in the node feature and workflow variable editors
Two authoring editors built a list of identical rows and gave a screen reader nothing to tell one row from another.
PluginSequenceFeatureBase::buildRow() wrapped each row in ['#type' => 'fieldset'] with no title, so a node's Escalation timers and its Assignments both presented as a run of groups with no accessible name — an untitled fieldset renders an empty legend, which is worse than none: the group is announced and has nothing to announce. That is the base class both features are built on, so it was every such editor.
WorkflowVariablesForm::row() gave the name and value fields an invisible title, which is right for a table, but the same one on every row: a reader heard Variable name, edit text once per declared variable with nothing saying which variable it belonged to.
The project had already answered this once. ConditionTreeBuilder carries the finding and the fix in one comment: "#title renders an empty legend, and a screen-reader user building an All/Any condition heard one unnamed group per row. The position is what tells them apart, counted as the list stands." Two more sites already name the row — VariablesTask's Order for @label and WorkflowMigrateForm's Tokens on "@node" move to.
What changed
PluginSequenceFeatureBasetitles each row, from a new overridablegetRowTitle(int $position). The position is counted as the list stands, not taken from the row's synthetic key, because removing a row leaves a gap in those keys and the number a reader is told has to match the row they are on.TimersandAssignmentsoverride it with their own noun: Timer 1, Assignment 1. The base default is Row @number, so a third-party sequence feature distinguishes its rows without changing anything — no new required key ongetLabels(), so nothing extending this@apiclass breaks.WorkflowVariablesFormnames each row's fields after the variable the row holds (Name of variable channel), and an empty row, which has no name yet, after its position (Name of new variable 1).- French for all seven new strings, in the base module's own catalog.
Tests
NodeFeatureEditorTest::testEveryRowIsNamedAfterWhereItStands()builds both shipped sequence editors with two rows each and asserts each row is afieldsetcarrying a distinct, non-empty title. Run without the fix it fails on'Timer 1'against''; with it, green.WorkflowVariablesUiTest::testDeclaringInitialVariables()now reads the six rendered labels off the page and asserts no two rows share one, which is the defect itself.
Checks
phpcs Drupal (1,056 files, 0), DrupalPractice (0), phpstan level 5 [OK] No errors, cspell clean on the changed files, scripts/check-translations.php exit 0 with no missing French and no orphans, and NodeFeatureEditorTest green against this branch in a lean root.