Overview
There are some transformations and validation of prop data that cannot be done purely client side: this is why experience_builder_preprocess_media_library_item__widget() plus the isMediaPreview logic in ui/src/components/form/inputBehaviors.tsx exist.
Proposed resolution
Eliminate experience_builder_preprocess_media_library_item__widget() and the isMediaPreview logic in ui/src/components/form/inputBehaviors.tsx, to prove that we can achieve that in a generic way that'll work for all field widgets.
- Support server-side model transformation and validation. Rely on
ClientServerConversionTrait::clientModelToInput()callingComponentSourceInterface::validateComponentInput(), which for SDC and "code" components calls:
$resolvedInputValues = array_map( // @phpstan-ignore-next-line fn(array $prop_source): mixed => PropSource::parse($prop_source) ->evaluate($entity), $inputValues, );… which is how we can make it so that
"target_id": "434"on the client (coming from a field widget) is transformed tosrc+alt+width+heightfor example.Contrast
$inputValueswith$resolvedInputValuesin the screenshot:

- Reduce the # of requests: the new
/xb/api/component-update/…route's response also provides the updated preview. (And it needs less data to be sent from client to server.) - From a robustness POV, there are consequences: https://git.drupalcode.org/project/experience_builder/-/merge_requests/5...
- From an architecture POV, this changes the client-side
modelto no longer contain "resolved" explicit inputs (i.e. the exact key-value pairs expected by SDCs), but the "source" explicit inputs (i.e. those stored in field instances and edited through field widgets).
User interface changes
None, other than faster updates 😊
| Comment | File | Size | Author |
|---|---|---|---|
| #31 | Screenshot 2025-02-07 at 3.10.38 PM.png | 478.57 KB | wim leers |
| #13 | Screenshot 2025-02-04 at 4.19.17 PM.png | 600.78 KB | wim leers |
| #24 | xb_form_update.gif | 607.19 KB | lauriii |
| #31 | Screenshot 2025-02-07 at 3.11.21 PM.png | 542.51 KB | wim leers |
Issue fork experience_builder-3499550
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
larowlanComment #4
larowlanOpened a draft MR based on work that was in the parent meta. Pausing for now as there are higher priorities in the parent.
Comment #5
wim leersPer #3500994: Remove references to 'props' outside of SDC - use 'inputs' instead 😊
Comment #6
wim leersAs described at #3500385-6: Display validation errors in "page data" (content entity form), AFAICT this blocks being able to fix the entire envisioned/expected scope of that issue?
Comment #7
effulgentsia commentedComment #8
larowlanPausing this to work on #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms
Remaining tasks:
\Drupal\experience_builder\Controller\ApiPreviewController::updateComponent\Drupal\experience_builder\PropSource\StaticPropSource::massageFormValuesTemporaryRemoveThisExclamationExclamationExclamationto mimic form submission aspects of\Drupal\experience_builder\ClientDataToEntityConverter::setEntityFieldsand\Drupal\Core\Entity\Entity\EntityFormDisplay::extractFormValues- the e2e fails are because form values for things like datetime need to make use of valueCallbacks on render elements as well asmassageFormValues. We're already doingmassageFormValuesand are hacking our way around#element_validatecallbacks (which can also set the widget value) - but we need to bite the bullet and make use of the form submitter because of things like value callbacks on element plugins. Hopefully there's some logic in\Drupal\experience_builder\ClientDataToEntityConverter::setEntityFieldsthat we can break out into a helper service or similar. Then the pieces of\Drupal\Core\Entity\Entity\EntityFormDisplay::extractFormValuescould probably go into component source specific plugins that make use of widgetsWe're fighting a lot of flexibility and inconsistency between widgets and entity data already, but element validate and value callbacks make things harder still.
Comment #9
wim leersReviewed #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms in-depth. Reviewed this MR at a high level. +1'd your pivotal suggestion at https://git.drupalcode.org/project/experience_builder/-/merge_requests/5...
Comment #10
wim leersThis blocks at a minimum:
Bumping priority and tagging to reflect that.
Comment #11
lauriiiWould it be possible to describe what the plan is on a high level? Does the change being proposed here impact the diagrams in https://git.drupalcode.org/project/experience_builder/-/blob/0.x/docs/di...?
Comment #12
wim leers#11:
The absence of such a write-up is precisely why I found it hard to jump in and help make progress here and all other child issues of #3467954: META: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc.. Which is why I spent the entire day so far reviewing all of 'em and attempting to connect the dots that @larowlan sees in his mind but hasn't yet written down in .
Note that #3499554: Move client-side assumptions about prop form data shape into a series of prop-specific transforms already made significant modifications, and it did update
docs/redux-integrated-field-widgets.mdbut notdocs/diagrams/react-editor-high-level.md. OTOH, that diagram is so high-level, that it actually still is accurate. 😅 All of this relates to theRenders Component formandGenerates through user interactionlabels in that diagram.Started reviewing a while ago, and the introduction of
ApiPreviewController::updateComponent()already surprised me given the issue title! 😅Comment #13
wim leersExcept … that all that is still happening, but in an indirect way! This MR leverages almost all of XB's abstractions that currently exist. It relies on
ClientServerConversionTrait::clientModelToInput()callingComponentSourceInterface::validateComponentInput(), which for SDC and "code" components calls:… which is how @larowlan is able to make it so that
"target_id": "434"on the client (coming from a field widget) is transformed tosrc+alt+width+heightfor example.(Screenshot attached. Contrast
$inputValueswith$resolvedInputValues.)Issue summary updated.
Responded there in detail. I'd like @jessebaker and @longwave at minimum (and preferably also @bnjmnm) to review what @larowlan is proposing/describing in that MR comment, because this a drastic change on at least 3 fronts.
Comment #14
wim leersFYI, back in September I created https://git.drupalcode.org/issue/experience_builder-3463842/-/compare/0.... as a way to get server-side massaging+validating of values passed back to the client — but it only works for AJAXy field widgets.
Comment #17
wim leersWith #3493941: Maintain a per-component set of prop expressions/sources now in, this is now the next one in the plan at #3467954: META: Evolve XB UI's data model to allow non-SDC components' inputs, DynamicPropSource support, etc..
Let's try to push this further now that @jessebaker and @bnjmnm have given their blessing over at https://git.drupalcode.org/project/experience_builder/-/merge_requests/5... 😊 (Plus, @larowlan explained how this brings us one small step closer to #3492059: [META] Conflict-free concurrent editing, too.)
Comment #18
wim leersWhew, that conflicted massively with #3493941: Maintain a per-component set of prop expressions/sources. I'm not familiar enough with XB's front-end codebase to be productive in there 😅 I did my best, but probably screwed some things up!
I resolved every conflict to the best of my ability, except for that in
ui/tests/fixtures/layout-default.json— we can do that more easily in #3504494: OpenAPI spec insufficiently precise for `LayoutComponent`.There is at least one clear next step:
\Drupal\experience_builder\Controller\ApiPreviewController::updateComponent()currently requires'propSources', but that makes no sense forBlockcomponents:Attempted to fix that :)
Comment #19
wim leersI can't have done the worst job, because now 15 e2e tests are passing compared to >11 before.
Comment #20
larowlanAddressed #18 - it is much simpler than that now that #3493941: Maintain a per-component set of prop expressions/sources is in, we just send the model. That's it. Rebased, then squashed to do that and then removed #3492061: Include the preview HTML in the layout controller payload
Will start on the review feedback and the remaining tasks (including fixing tests).
Comment #21
wim leersThis already looks fantastic now!
Down to one failing e2e test.
Thanks to your overnight rebase+simplification (thanks! 🤩👍) of the MR, I think one more thing revealed itself as being unnecessarily complicated — because thanks to this MR, things can be simplified a lot. I'd swear you wrote something similar somewhere else, but can't find it.
Either way: I'm excited about what I'm seeing! 🥳
I'd love for @longwave to review this from the BE perspective and @bnjmnm or @balintbrews from the FE perspective, to enable @larowlan to drive this home on his last workday prior to PTO! 😄
Comment #22
wim leersHm the last failing e2e test also failed during nightly testing, like this:
And over here, that same test passes that bit, but fails shortly after that.
Comment #23
wim leersThis looks amazing! 😄
Reviewed: https://git.drupalcode.org/project/experience_builder/-/merge_requests/5...
Pushed 2 tiny commits (one to simplify reviews by moving code back to the original location, one with language nits).
I've asked @longwave (BE) and @balintbrews (FE) to review this, and to fix the one (BE) regression I spotted.
Comment #24
lauriiiI tested this locally and during the update validation the form disappears and reappears after the validation is done. I hope this is no fundamental because this would be a major regression to the user experience and would simply not be acceptable.
Comment #25
lauriiiDiscussed with @wim leers that #24 is a regression that was introduced here during the last 24h and should be addressed in this issue. 👍
Comment #27
wim leersYes, of course, but the overall MR definitely needs review. To potentially surface more regressions, but hopefully not :D
@longwave is currently working on a review 👍
Also crediting @jessebaker and @bnjmnm for their pre-emptive review/guidance at https://git.drupalcode.org/project/experience_builder/-/merge_requests/5....
Comment #28
larowlanThat's the style tag added in DummyPropsEditForm, it can go, it was something I tried to get the test passing
Comment #29
longwaveSo the issue that Wim raised where "Alternative text is required" appears is because in the XB preview
#valuefor the image field isbut if I do a normal form submission without uploading an image, many of those items - including alt, which is the problem here - are not present:
In managed file fields, the alt text input doesn't appear until an image has been selected for upload, at which point the value array is:
I think just skipping validation errors will skip the case where an image is selected and alt text is required?
Comment #30
longwaveI think this is a front end problem. The layout endpoint returns all the entity field data, but for the preview we should only be sending the input values that are actually present in the form instead of keeping all the original data and overwriting values that have changed.
edit: digging into the form build process a bit more,
alt,titleanddisplayare added byImageWidget::process()but#accessis set to FALSE if there is no valid file. This means that the empty value is present in the form state - but no element is rendered on the front end and so the value is not expected to come back when the form is submitted. If this is not considered a front end issue we could pre-filterentity_form_fieldsso only values with#access => TRUEare sent to the front end in the first place?Comment #31
wim leersThat
#access ⇒ TRUE|FALSEsure sounds like a likely culprit. And even quite possibly a looming information disclosure vulnerability? The client should never even be able to know/see that information? The server should indeed prevent it from being sent to the client!When looking at a node whose

field_imageis populated:When looking at another where it's still empty (should be fine because it's optional):

So I think @longwave is right: we need to find the root cause for
field_image[0][alt]: ""being included among the server-providedentity_form_fields.I bet the complexity lies in how XB interprets
ImageWidget'sThose last 3 are all relating to the conditionality.
Comment #32
longwaveI've added filtering to
ApiLayoutController::getEntityData()to remove values fromentity_form_fieldsthat have#access => FALSEand should not be accessible by the client - this might leak information to the client even if it's not displayed in the form, so it needs to be done server side.However this doesn't fix the error; even though
field_image[0][alt]is not present in the payload any longer, the validation is still triggered - run out of time today to figure out why this is happening.Comment #33
larowlanThis doesn't happen in head because we don't use the converter service, we use a custom convert method in preview controller
I vote we revert back to that and sort it separately
Comment #34
bnjmnmdfaf6b18 takes care of the alt validation message. The form generated by
setEntityFieldswas not taking access into account so it was validating fields that should be ignored as their access is false.Comment #35
larowlanThanks @bnjmnm, we should revert https://git.drupalcode.org/project/experience_builder/-/merge_requests/5... then as I did that as a temporary workaround
Comment #36
larowlanI confirmed #34 solved the issue so I reverted f3fd9d2f
#34 changed the behaviour in HEAD - previously if you sent a field you didn't have access it would generate an error.
Now the field is ignored.
I updated the test to reflect this and it now passes - that is 9592dd2a
Would be good to get a +1 from Ted/Wim/Lauri/Alex on the new behaviour
This should be green again and the 2 bugs are now fixed.
Comment #37
longwaveAdded a test for
::filterFormValues(), added some comments and cleaned up some minor bits, this is ready for review again.Comment #38
wim leers#34 🤯 Wow 😄
@bnjmnm comes in to save the day with a one-line change 👏
Comment #39
wim leersI have no more remarks about the BE pieces. As the person who built the
ComponentInputsFormand surrounding infrastructure from the ground up, I think it should be @bnjmnm reviewing the FE pieces and doing the final sign-off 😊Comment #41
longwaveFixed the merge conflict.
Comment #43
wim leersNext up:
Comment #44
effulgentsia commentedComment #46
wim leers