Problem/Motivation

Component developers evolve their components over time: adding or removing props and slots, changing prop types, updating widgets, adjusting requiredness, or modifying prop expressions. Since component instances stored in Canvas pages, templates, and regions reference a specific Component config entity version (see #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), a mechanism is needed to upgrade existing instances to newer versions when the component definition changes.

Without such a mechanism, content creators would be stuck on old widget UX, stale prop definitions, or orphaned data from removed props.

Proposed resolution

A ComponentInstanceUpdater plugin system was introduced that automatically upgrades component instances when a Canvas entity (page, template, region) is opened for editing. The system is conservative: it only auto-upgrades when the change has no destructive data storage implications.

Core infrastructure

  • ComponentInstanceUpdaterInterface defines isUpdateNeeded(), canUpdate(), and update() methods. The primary implementation is GeneratedFieldExplicitInputUxComponentInstanceUpdater.
  • Auto-upgrade on edit: when a Canvas entity is opened for editing, ComponentSourceManager::updateComponentInstances() runs automatically, upgrading all instances where safe. Modified trees are auto-saved, so the editor has the last word on publishing after previewing the changes.

Safe upgrade criteria (auto-upgraded)

Unsafe changes (NOT auto-upgraded)

Both of these would cause data loss:

Unsafe changes — auto-upgraded for CODE COMPONENTS

⚠️ But for code components, we specifically want to allow both of those unsafe changes, because the reality is already that such changes are allowed to be made to the code component itself:

Therefore, we must introduce a JsComponent-specific instance updater that extends GeneratedFieldExplicitInputUxComponentInstanceUpdater and allows 2 additional update operations:

  1. Code-component-only: drop stored explicit inputs in old component instance for props whose shape has changed in active version #3587711: Introduce a JsComponentInstanceUpdater that allows prop shape changes and accepts data loss

→ both of these should inform the content author of the data loss in a toast, see #3574257-6: Provide a toast informing editors about which Canvas component trees have been auto-updated, point 2.

Specific scenarios handled

User interface changes

#3574257: Provide a toast informing editors about which Canvas component trees have been auto-updated

Original report

Discovered while discussing #3461499-17: Support complex SDC prop shapes: introduce (Storable)PropShape to compute field type storage settings in-depth with @lauriii.

Hypothetical scenario:

  1. A component has a prop with the schema {type: string, format: date}.
  2. This component has that prop populated by a StaticPropSource that uses core's datetime field type, with the datetime_type setting set to date.
  3. The Site Builder decides that actually, this should be handled by a different field type: the timestamp field type, plus the unix_to_date adapter (which already exists: \Drupal\experience_builder\Plugin\Adapter\UnixTimestampToDateAdapter).

Existing content will continue to work, because the StaticPropSource is self-contained component instance is coupled to a Component version (since #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). It will continue to use the datetime field type.

But this also means the UX will be different: a different widget is presented.

So the Content Creator should have the ability to update the component's existing stored props to whichever the updated default field types are for each prop shape.

⚠️ For now this is pretty hypothetical: moving from one field type to another is not very likely because #3464003: [PP-1] [later phase] [needs design] Introduce "adapters" UX. Changing the widget is far more likely.

CommentFileSizeAuthor
Screenshot 2024-07-25 at 2.40.07 PM.png575.36 KBwim leers

Comments

Wim Leers created an issue. See original summary.

Wim Leers credited lauriii.

wim leers’s picture

wim leers’s picture

Issue summary: View changes
wim leers’s picture

Title: [later phase] When the field type for a PropShape changes, the Content Creator must be able to upgrade » [later phase] [PP-1] When the field type for a PropShape changes, the Content Creator must be able to upgrade
Issue summary: View changes

Just realized that this is actually far out because of #3464003: [PP-1] [later phase] [needs design] Introduce "adapters" UX.

wim leers’s picture

Version: » 0.x-dev
Component: Page builder » Data model
Related issues: +#3515629: FieldWidget's XB transforms must be bubbled by the Field Widget rendering to inform the client
wim leers’s picture

wim leers’s picture

wim leers’s picture

Issue tags: +post-stable priority
wim leers’s picture

Closely related: #3528284: Add e2e tests that prove we can edit an old version of a component. That will lay the foundations for the necessary test coverage for this issue.

wim leers’s picture

Title: [later phase] [PP-1] When the field type for a PropShape changes, the Content Creator must be able to upgrade » When the field type for a PropShape changes, the Content Creator must be able to upgrade
Version: 0.x-dev » 1.x-dev
Status: Postponed » Active

I think #5 is actually wrong — this is not blocked on adapters. Because at least a common subset is in principle possible to realize already.

Project: Experience Builder » Drupal Canvas

Experience Builder has been renamed to Drupal Canvas in preparation for its beta release. You can now track issues on the new project page.

wim leers’s picture

Title: When the field type for a PropShape changes, the Content Creator must be able to upgrade » When the field type or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Related issues: +#3532514: Gracefully handle components in active development: ensure great DX

A crucial piece of metadata was missing that would've prevented building this UX: tracking of which props are required #3532514: Gracefully handle components in active development: ensure great DX's first MR (!205) fixed that. 👍

That means this issue is now able to:

  • Compare requiredness of each prop
  • Compare field type, storage settings, instance settings, widget and default value of each prop
  • Compare the set of slot definitions of each prop (which slots exist, their order and the example values for each)

Which should be enough to build a first iteration

wim leers’s picture

Title: When the field type or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Priority: Normal » Critical
lauriii’s picture

mglaman’s picture

I think this is another scenario: could be triggered by an image code component and someone creating another media type with an image media source.

larowlan’s picture

I think perhaps this could/should be a child of #3509115: [META] Plan to allow editing props and slots for in-use code components as it is dealing with the practical implications.

e.g with #3556340: Support deleting a prop from an in-use code component and #3524401: `GeneratedFieldExplicitInputUxComponentSourceBase::validateComponentInput()` allows garbage to pile up we can't actually do GC because if the prop exists in the older version and that version is in use, we have to retain the values. But upgrading to the latest version allows us to do that GC

Thoughts?

lauriii’s picture

I'm second guessing myself from before by wondering if we should just automatically update all instances when the page is being edited. I'm not sure what the benefit of the 'Update to latest version' would be given that we would still render the component using the latest version. The only reason this could make sense is that if fields are removed, you could at least copy the values and move them somewhere. However, given that you wouldn't know what's being changed, it doesn't seem all that useful. If we wanted to solve for that, maybe we need a way to access stale data that was attached to a component before? It seems like that could be an incremental addition we would add at a later point.

wim leers’s picture

Title: When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Issue summary: View changes
Status: Active » Postponed

FYI: #3558719: Linking then unlinking a field renders a wrong Field Widget: corrupt `StaticPropSource` is sent by client, server should detect this is about to land, and I have +1s for the direction I'm taking #3560005: Require component instances to use the field type + storage + instance settings dictated by the `Component` version in, which will make this MR way simpler and more feasible.

Which also means that @lauriii's #20:

I'm second guessing myself from before by wondering if we should just automatically update all instances when the page is being edited.

… will actually become feasible for many things! 🚀

In a nutshell, we would be able to auto-update the component_version for a component instance that has:

  1. a new Component version due to added optional props: automatic update possible (zero inputs changes)
  2. a new Component version due to deleted props: automatic update possible (omit obsolete key-value pairs in inputs upon re-saving)
  3. a new Component versions due to changed field widget (whether in Canvas itself or due to hook_storage_prop_shape_alter()): automatic update possible (zero inputs changes)

👆 IMHO those are the scope of this issue. They result in old content entity revisions remaining unchanged, new content entity revisions using "the latest and greatest".


But … even for BC break scenarios, update paths become possible, but do inevitably require tailored update paths (i.e. code).

  1. New Component versions due to changed field type/storage settings/instance settings (whether in Canvas itself or due to hook_storage_prop_shape_alter()): automatic update possible IF the developer provided an update path.

    👉
    A) load all Components that contain >=1 props of the affected prop shape (will become easier after #3547579: Introduce a new cache tags aware prop shape repository, so changes affecting prop shape calculation can force the re-invoke of hook_canvas_storable_shape_prop_alter)
    B) iterate over all versions (Component::getVersions())
    C) find all instances using that version (ComponentAudit)
    D) per component instance, perform whichever logic is necessary — e.g. link to uri field type is a simple transformation, but image to (media) entity_reference is harder: requires an intermediary Media entity to be created for the previously reference File entity.
    E) set the component_version to the new ("active") version
    F) either save as a new content entity revision (preferable, would keep past data/revisions, and they would all still work!) or as an updated config entity (which do not support revisions — so the following component instance-containing config entity types would be updated in-place: ContentTemplate, PageRegion and Pattern)

    NOTE: this does NOT need to happen in a single request. It could (and must) be done in interruptible batches. State tracking (to immediately continue where we left) would accelerate such an update path, but it'd always be possible to reconstruct the remaining work, too (thanks to ComponentAudit).

    See the preparations for this from June 2025 in \Drupal\Tests\canvas\Kernel\Plugin\Canvas\ComponentSource\ComponentInputsEvolutionTest::testStorablePropShapeChanges().

  2. New Component versions due to renamed props or added required props: automatic update possible IF the SDC developer provided an update path.

    👉
    A) load the Component
    B) iterate over all versions (Component::getVersions())
    C) find all instances using that version (ComponentAudit)
    D) per component instance, perform whichever logic is necessary — e.g. rename a key in the component instance's input to the new prop name, or populate a new required prop with a default value
    .
    E) set the component_version to the new ("active") version
    E) either save as a new content entity revision (likely preferable, would keep past data/revisions, but none of them would work, because the live component implementation requires data) or not — it's up to the SDC developer to decide

effulgentsia’s picture

penyaskito’s picture

Title: [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Status: Postponed » Active
mglaman’s picture

Assigned: Unassigned » lauriii

The proposed resolution needs to be revisited after #20 and #21.

@lauriii specifies we should auto-update, but in #21 only three cases of auto-updates are identified. What do for components which fail those qualifications? Especially with code components.

lauriii’s picture

Assigned: lauriii » Unassigned

Limiting the automatic updating to the three scenarios from #21 is not sufficient. The scenarios that require developer-provided update paths would create a significant burden for component authors. It shouldn't be on them to provide migration paths for changes to props.

We need to be able to handle all scenarios for prop and slot modifications. This is already the case for content modeling outside of Canvas components in Drupal, including Blocks. When you change field configuration for Blocks and other fieldable entities, it's reflected everywhere that entity is used without requiring manual upgrade path. We should match that experience.

effulgentsia’s picture

I think we should break this up into manageable steps. I wrote up #3567413: When editing a Canvas entity with a component tree (e.g., page, template, or region), automatically upgrade component instance versions for cases where there's no impact on existing data as the first step. I'll write up the additional steps in the coming days.

wim leers’s picture

Title: When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Status: Active » Postponed
penyaskito’s picture

penyaskito’s picture

wim leers’s picture

Title: [PP-3] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Assigned: Unassigned » penyaskito
penyaskito’s picture

Title: [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [PP-2] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Related issues: +#3574026: Component instances are not updating in the same way in regions than other entities
wim leers’s picture

Title: [PP-2] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Issue tags: +Needs issue summary update
nagwani’s picture

Issue tags: +triaged
wim leers’s picture

Just noting that once this is done, we should determine next steps on #3557271: [PP-2] BE: Support changing the data-type of a code component prop.

EDIT: link fixed.

wim leers’s picture

Title: [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [META] [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Category: Task » Plan

Reflecting the reality that all the actual work has been happening in child issues thanks to @effulgentsia's #29.

wim leers’s picture

Status: Postponed » Needs review

Actually, should we close this? Is this now just a duplicate of #3547808: label_display block configuration value should not be translatable?

penyaskito’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Updating issue summary based on what was implemented already.


Still missing: what to do if the prop shape changes. I think I filled an issue for that, but cannot find it right now. Edit: #3524751: [later phase] Component Source plugins: generalized support for schema changes of explicit inputs should work for that.

Leaving this open to create that, and decide what to do with all the child issues still open.

penyaskito’s picture

I think #39 meant #3509115: [META] Plan to allow editing props and slots for in-use code components.

This META is for the infra needed to be able to update component versions on existing trees.
#3509115 is about using this to allow the UI to edit Code Components. There's a close relationship between the two, but I wouldn't say they are duplicates.

The issue I was missing in #40 should be #3524751: [later phase] Component Source plugins: generalized support for schema changes of explicit inputs, added as related now.

penyaskito’s picture

Issue summary: View changes
penyaskito’s picture

penyaskito’s picture

wim leers’s picture

Title: [META] [PP-1] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade » [META] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade
Issue summary: View changes

#3463996: [META] When the field type, storage/instance settings, widget, expression or requiredness for an SDC/code component prop changes, the Content Creator must be able to upgrade has happened and means that "multiple-cardinality props" aka "multi-value props" aka "type: array props" has surfaced a number of edge cases. For example #3587711: Introduce a JsComponentInstanceUpdater that allows prop shape changes and accepts data loss.

This made me realize that actually #3577946: Remove Canvas Dev Mode flag for Multi-Value Props support should not have happened until some things here had been tackled:

  1. Safe upgrade criteria (auto-upgraded) should contain "increase of cardinality"
  2. Unsafe changes (NOT auto-upgraded) should contain "decrease of cardinality"
  3. Unsafe changes (NOT auto-upgraded) for code components should probably allow decreasing the cardinality for code components (meaning a JsComponentInstanceUpdater extends GeneratedFieldExplicitInputUxComponentInstanceUpdater that discards old values) — see #3587711-14: Introduce a JsComponentInstanceUpdater that allows prop shape changes and accepts data loss (and that have been updated before #3577946: Remove Canvas Dev Mode flag for Multi-Value Props support landed)

And that in turn made me realize that another thing was missed in this meta, unrelated to #3577946's multiple-cardinality work:

  1. Unsafe changes (NOT auto-upgraded) for code components should probably allow changing the prop shape for code components (meaning a JsComponentInstanceUpdater extends GeneratedFieldExplicitInputUxComponentInstanceUpdater that discards old values) — see #3587711-14: Introduce a JsComponentInstanceUpdater that allows prop shape changes and accepts data loss

(Added all of those to the issue summary. LMK what you think, @penyaskito.)

P.S.: Apparently I'm the one who marked this "PP-1" almost 2 years ago in #5. That's no longer relevant.

penyaskito’s picture

Issue summary: View changes

#45: I was scared for a second. We are NOT auto-upgrading if cardinality changes at the moment.

    // Cardinality, field storage settings and field instance settings all are
    // optional. If not specified, they default to the respective defaults.
    if (($this->cardinality ?? self::DEFAULT_CARDINALITY) !== ($target->cardinality ?? self::DEFAULT_CARDINALITY)) {
      return FALSE;
    }

Edited IS to point that Increase of cardinality (more values allowed) needs an issue too.

wim leers’s picture

Yes, sorry, my bad — indeed all 4 of those still need to be implemented!

Other than that, does #45 make sense?

penyaskito’s picture

#45 makes sense. We could even go further and when "decrease of cardinality" happens, we could check at the instance level if that instance still fits. e.g. if cardinality was 5, I'm reducing to 3, and a given instance has 3 values, it could be allowed.

Could be confusing UX-wise, but not worse than adding an instance to a tree that has instances than cannot be auto-updated.

wim leers’s picture

Issue summary: View changes

#48: yep, that's definitely what I intended: only drop the "excess" values. We should minimize data loss for sure.

Clarified in IS.

wim leers’s picture

Issue summary: View changes

Repurposed #3587711 to solve one of the 4 todos listed in #45.

wim leers’s picture

Issue summary: View changes

AFAICT #3574257: Provide a toast informing editors about which Canvas component trees have been auto-updated's blockers have landed. It would really help in informing the user of the unsafe changes allowed for code components.

Also, child issue #3574257: Provide a toast informing editors about which Canvas component trees have been auto-updated was not listed as a user interface change yet, so added that.

wim leers’s picture

Issue summary: View changes

Quoting @lauriii:

NOT support cardinality decrease

I don't see the logic behind this; we support deleting props which is more severe data loss than decreasing the cardinality

Wow Removing props (required or optional) is indeed considered safe, I forgot about that! @lauriii decided that Jan 29 apparently, in
#3568906-3: Handle upgrading and rendering not yet upgraded component instances of components with newly removed props or slots.

That simplifies things.