Overview
Discovered at #3532514: Gracefully handle components in active development: ensure great DX:
While manually testing the fix for the reported bug (see @heyyo's #7), I couldn't get to the point where I had 2 component instances of different version in the component tree. First I thought "PEBKAC". And it was. While editing, I got 2 different versions 👍 But then upon saving, they both changed to the same version! 😱
IOW: the component version is always overwritten to the active component version, instead of retaining the original component version.
This appears to be a bug in the
Auto-savefunctionality because the client sends the correctComponentversions:
but the corresponding auto-save entry contains this:
[field_xb_demo] => Array ( [0] => Array ( [uuid] => 89b1e8ac-ac5d-459a-94b9-f7a6d2f0b257 [inputs] => {"text":"The og heading element","style":"primary","element":"h1"} [component_id] => sdc.experience_builder.heading [parent_uuid] => [component_version] => acbd38ffd9d42d04 [slot] => [label] => ) [1] => Array ( [uuid] => c0d4e9e1-23c0-46ee-9a71-a75d6a5494cb [inputs] => {"style":"primary","text":"Dramatic title!","element":"h1"} [component_id] => sdc.experience_builder.heading [parent_uuid] => [component_version] => acbd38ffd9d42d04 [slot] => [label] => ) [2] => Array ( [uuid] => 770f670f-5526-4704-9c55-8ac28b022751 [component_id] => sdc.experience_builder.heading [component_version] => acbd38ffd9d42d04 [inputs] => {"text":"drama","style":"primary","element":"h1"} [parent_uuid] => [slot] => [label] => ) )… i.e. everything on
acbd38ffd9d42d04aka the latest version.
— #3532514-12: Gracefully handle components in active development: ensure great DX
+
I've traced #12 to a bug in
\Drupal\experience_builder\ClientDataToEntityConverter::convert(), specifically:$item_list->setValue(self::convertClientToServer($layout['components'], $model, $entity, $validate));… but not through a flaw in
Auto-savefunctionality! It appears to be a bug in\Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem::setValue()and::onChange()🫠
— #3532514-13: Gracefully handle components in active development: ensure great DX
See attached screencast of the data loss happening in real time while debugging.
Proposed resolution
User interface changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| xdebug component_version data loss.mov | 13.8 MB | wim leers |
Issue fork experience_builder-3536277
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 #2
wim leersThis blocks fixing #3532514: Gracefully handle components in active development: ensure great DX.
This regression appears to have been introduced in #3523841: Versioned Component config entities (SDC, JS: prop_field_definitions, block: default_setting, all: slots for fallback) + component instances refer to versions ⇒ less data to store per XB field row.
Comment #4
wim leersSo turns out this is literally doing what I was concerned about:
😅
Test coverage pushed 👍
Comment #5
wim leersTest coverage is proving the problem. 👍
AFAICT the root cause is
this bit in
\Drupal\experience_builder\Plugin\DataType\ConfigEntityVersionReference::setValue():when gets called with that string value from
\Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem::onChange()where it does:(at this point,
$property2 == 'component'and$property1 == 'component_id', which of course does not include the component version, so the stringConfigEntityVersionReference::setValue()receives is indeed only a component ID)in combination with
in
\Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem::onChange().Now, that wouldn't have been a problem if it would've been "corrected" subsequently, which doesn't happen because …
in combination with
in
\Drupal\experience_builder\Plugin\Field\FieldType\ComponentTreeItem::onChange()incorrectly concluding that no version is specified when in fact it was, but …in combination with
\Drupal\Core\TypedData\Plugin\DataType\Map::setValue()'s👆 that
unset()leadingComponentTreeItem::onChange()to reasonably deduce that in fact no version was specified 🫠🤪 What a crazy crazy combination of circumstances. Death by a thousand cuts.
Comment #6
wim leersThis is ready for final review — preferably by @larowlan, because he's the original author of these pieces, and I doubt we have anybody else on the team with the same level of Field API/Typed Data API expertise 😅
Comment #9
larowlanAwesome work @wim leers
Merged to 0.x
This was always going to be tricky, I remember how hard these were to get right for Dynamic Entity Reference and I think Entity Reference Revisions probably had similar challenges. I wonder if we should look to make version a required key when calling setValue - it would mean a lot of changes in tests but the 'automatically picking the latest version if not found' logic has bitten us a couple of times now.