Require component instances to use the field type + storage + instance settings dictated by the `Component` version
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3560005. -->
Reported by: [wim leers](https://www.drupal.org/user/99777)
Related to !358
>>>
<h3 id="overview">Overview</h3>
<p>@jessebaker on <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3558719" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3558719</a></span>'s MR:</p>
<blockquote><p>
> I do wonder if the API should be relying on what data the client is sending to this PATCH endpoint. Does the server know the component that is being patched and can it therefore access the correct/original 'sourceTypeSettings' rather than having to rely on them being sent in the request.
</p></blockquote>
<h4>Background: intentionally stateless</h4>
<p>The <code>/canvas/api/v0/form/component-instance/{entity_type}/{entity}</code> internal HTTP API (so the <code>\Drupal\canvas\Form\ComponentInstanceForm</code> controller) was designed to do be stateless: it generates a form for a component instance, and is given all the necessary information.</p>
<p>IOW: it's intentionally stateless (other than Drupal AJAX form state); the server is told:</p>
<ul>
<li>which <code>Component</code> the component instance is of</li>
<li>the inputs for that component instance</li>
<li>the UUID of the component instance</li>
</ul>
<p><strong>However…</strong></p>
<p>That architecture dates back all the way to early XB/Canvas days. It was introduced in July 2024: <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3461422" title="Status: Closed (fixed)">#3461422: Evolve component instance edit form to become simpler: generate a Field Widget directly</a></span>.</p>
<p>Since then, we gained versions <em>within</em> <code>Component</code> config entities (<span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3523841" title="Status: Closed (fixed)">#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></span>), which also made sure that the client model's "component" identifier gained a version: <code>componentID@componentVersion</code>. And within each version, we store exactly the kind of information you're referring to!</p>
<p>So, in principle, it _is_ now possible to do what you say. The only caveat is that in principle, we allow each component instance to <em>deviate</em> from what the associated <code>Component</code> version dictates. For example, version <code>1sdfsdf</code> might have picked the <code>url</code> field type for some prop, but version <code>2badb</code> might have picked the <code>link</code> field type.</p>
<p>Now, for the tricky bit: it's not <em>required</em> for a component instance to comply with this — all Canvas cares about is storing a prop source that is able to populate the SDC or code component prop without causing validation errors. In the early days that seemed wise: it allowed for rapid iteration, freedom for component trees to do what they want, and so on. It also predates <code>Component</code> versions by over a year!</p>
<p>Today though, that seems like an unnecessary and even brittle ability. If we'd remove that ability to deviate from what the <code>Component</code> version specifies should be the field type (and storage + instance settings), we'd make update paths simpler. It'd also enable the <em>(un)collapsing</em> of stored data (introduced by @larowlan and I in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3523841" title="Status: Closed (fixed)">#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></span>) to be <em>guaranteed</em> rather than a "only if there was no deviation". Deviations were specifically allowed in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3538487" title="Status: Closed (fixed)">#3538487: Don't allow passing uncollapsed inputs if using default expression</a></span>. This is about disallowing that.</p>
<p><strong>👉 That would make <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3463996" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3463996</a></span> <em>far</em> easier to achieve, because all instances of a <code>Component</code> version would be guaranteed to be using the field type + storage settings + instance settings defined in the <code>Component</code> version!</strong></p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Quoting <code>\Drupal\canvas\Plugin\Canvas\ComponentSource\GeneratedFieldExplicitInputUxComponentSourceBase::collapse()</code>:</p>
<pre> /**<br> * Collapse prop source for storage whenever possible.<br> *<br> * StaticPropSources are dangling field item lists, which require a lot of<br> * metadata to be known: field type, storage settings, instance settings and<br> * expression.<br> * When a StaticPropSource is being stored (to populate some component prop),<br> * check if it matches that metadata in the `prop_field_definitions` for this<br> * component instance's referenced version of the Component config entity. If<br> * it does match, all metadata can be omitted, which significantly reduces the<br> * amount of data stored.<br> *<br> * @param \Drupal\canvas\PropSource\PropSourceBase $source<br> *<br> * @return OptimizedExplicitInput|PropSourceArray<br> * Either:<br> * - the collapsed prop source storage representation, which means either a<br> * scalar or an array without a `sourceType` key<br> * - the uncollapsed prop source storage representation, which means this<br> * will be an array with a `sourceType` key.<br> *<br> * @see ::uncollapse()<br> */<br> private function collapse(PropSourceBase $source, string $prop_name): mixed {</pre><ol>
<li>Remove ability for new Component instances to deviate, i.e. change the above to become a <em>requirement</em> rather than an <em>option</em>.
</li><li>… but to ease the transition, consider allowing a component instance to deviate if it had already previously been
</li><li>TBD
</li></ol>
<h3 id="ui-changes">User interface changes</h3>
> Related issue: [Issue #3461422](https://www.drupal.org/node/3461422)
> Related issue: [Issue #3523841](https://www.drupal.org/node/3523841)
> Related issue: [Issue #3538487](https://www.drupal.org/node/3538487)
> Related issue: [Issue #3463996](https://www.drupal.org/node/3463996)
> Related issue: [Issue #3520449](https://www.drupal.org/node/3520449)
issue