eca_render: action_base.weight typed as integer contradicts the token-capable textfield defaulting to an empty string
Problem
eca_render.action_base declares:
weight:
type: weight
label: 'Weight'weight is an integer type, but the form element is a token-capable textfield
that defaults to an empty string:
RenderElementActionBase::defaultConfiguration()sets'weight' => ''.buildConfigurationForm()builds it as atextfieldwith'#eca_token_replacement' => TRUEand'#required' => FALSE.execute()explicitly treats''as "no weight" and otherwise runs the value through token replacement.
This does not produce a schema validation error. StorableConfigBase::castValue()
sanitizes values before ConfigSchemaChecker ever sees them, so validation passes.
The actual consequence is silent data loss — a weight defined by a token is cast
to integer 0 on save:
| input | stored |
|---|---|
'' |
NULL |
'[my:weight]' |
0 |
'_eca_token' |
0 |
'7' |
7 |
Because the token never survives the save, the replaceClear() call in
RenderElementActionBase is unreachable dead code, and the user silently gets
weight 0 — itself a legal weight, so nothing surfaces.
Where it comes from
This is pre-existing: action.configuration.eca_render_build already inherited
it. It became more widespread with
#3590379 (closed), which added
the missing action.configuration.* sections for the remaining render actions —
those new sections inherit the same base. eca_htmx also descends from
eca_render.action_base, so the affected surface spans three sub-modules.
Proposed resolution
Complete the existing eca_integer_or_token convention from
#3556019 (closed) rather than
introducing a third pattern. Those types are never hand-written in a schema
file; they are applied by ConfigSchemaHooks.
Two structural gaps stop that convention from reaching this bug:
- Base-type keys are invisible to the alter hook.
hook_config_schema_info_alter()fires against raw per-file definitions, while type inheritance is resolved lazily afterwards inTypedConfigManager::getDefinitionWithReplacements(). A key declared on a shared base type such aseca_render.action_basecannot be reached by an alter that walks only leaf keys, so addingweightto the match arm alone is a silent no-op. The alter must walk the declared-type ancestry — with an allowlist, since rewriting a base type owned by another module would retype that module's configuration site-wide. - Events are never altered. The hook iterates actions and conditions only;
eca.event.plugin.*is untouched.
Corrections to the original summary
eca_render.element_action_basedoes not exist in either branch. Onlyeca_render.action_basedoes.tests/src/Kernel/PluginConfigSchemaTest.phpdoes not enforce this. It is a coverage test — it asserts each plugin has a schema definition whose type is notundefined, and passes whether or not the token is destroyed.- The problem is not a schema violation. Validation passes; the value is destroyed before the checker runs.
Change record: #3616767