[META] Review of changes and Conflict resolution
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3587587. --> Reported by: [f.mazeikis](https://www.drupal.org/user/3513671) >>> <h3 id="overview">Overview</h3> <p>This is a meta to track work to add ability to add the following:</p> <ul> <li>Add conflict detection when underlying entity that auto-save entry has been based on is updated outside of Canvas UI</li> <li>Add ability to detect and resolve such conflicts using Canvas UI</li> <li>Build UI that allows to use side-by-side comparison when resolving such conflict</li> <li>Ability to conveniently Review changes to multiple entities re-using much of the same new UI</li> </ul> <p>Refer to child issues for actionable steps.</p> <ul> <li>&#128994; Description and requirements: <ul> <li><a href="https://www.drupal.org#proposed-resolution-product-speak">&#128372;Product Speak</a></li> <li><a href="https://www.drupal.org#proposed-resolution-user-journeys">&#128372;User journeys</a></li> <li><a href="https://www.drupal.org#proposed-resolution-additional-criteria">&#128203; Additional criteria</a></li> </ul> </li> <li>&#128994; Proposed resolution: <ul> <li><a href="https://www.drupal.org#proposed-resolution-canvas-engineering-speak">&#128104;&zwj;&#128300; Canvas Engineering Speak</a></li> <li><a href="https://www.drupal.org#proposed-resolution-ui-changes">&#129299; Definitions</a></li> <li><a href="https://www.drupal.org#proposed-resolution-ui-changes">&#128119;&zwj;&#9792;&#65039;&#128119;&zwj;&#9794;&#65039; UI Changes</a></li> </ul> </li> <li><a href="https://www.drupal.org#adr">&#128993; ADR</a></li> <li><a href="https://www.drupal.org#designs">&#128994; Designs</a></li> <ul> <li><a href="https://www.drupal.org#designs-conflict-resolution">Conflict resolution UI</a></li> <li><a href="https://www.drupal.org#designs-review-changes">Review of changes UI</a></li> </ul> <li><a href="https://www.drupal.org#related-issues">&#128679;&#127959;&#65039; Related issues</a></li> <li><a href="https://www.drupal.org#out-of-scope">&#128683; Out of Scope</a></li> </ul> <h3><strong>Current problems</strong></h3> <ol> <li>Users reviewing changes in their autosave before publishing are unaware if the published content has been updated externally, risking silent overwrites of concurrent work. </li><li>When concurrent changes occur between the updated entity a user has in their autosave and the published version of the same entity a user started with, users lack visibility into the conflicting versions. </li><li>Relying on the &ldquo;preview&rdquo; functionality for individual entities in Canvas UI is insufficient when reviewing changes in autosave, as there is no comparison to currently published versions of entities. </li><li>Users don't have a quick and convenient way to visually review changes made to multiple entities. </li></ol> <h3>Proposed resolution</h3> <h3 id="proposed-resolution-product-speak">Product Speak:</h3> <p>Expected outcome from a Product POV, by <a href="https://www.drupal.org/u/lauriii"><u>@lauriii</u></a>:</p> <blockquote><p>We should create clear, visual interface to identify and resolve version conflicts before publishing. By flagging conflicts in the review panel and providing a side-by-side comparison modal per entity, users can confidently select the correct version to proceed with, preventing accidental overwrites. Same functionality can also be used to preview any non-conflicting changes vs the published version of the entity.</p> </blockquote> <h4 id="proposed-resolution-user-journeys"><strong>User journeys:</strong></h4> <p>Original user journeys written by <a href="https://www.drupal.org/u/lauriii"><u>@lauriii</u></a>, expanded and clarified with the help of <a href="https://www.drupal.org/u/mark-dodgson">@mark-dodgson</a>:</p> <h4>Conflict Identification</h4> <blockquote><p><strong>Given</strong> a user has an un-published autosave changes on an entity in Canvas</p> <p><strong>And</strong> that the same entity has been updated and saved before user published their changes</p> <p><strong>When</strong> the user clicks "Review Changes" to publish</p> <p><strong>Then</strong> the specific entity is visibly flagged with a "Conflict" indicator in the review panel and cannot be selected for publishing</p> <p><strong>And</strong> a dedicated "Conflict resolution CTA" section appears in "Review Changes" panel above the list of entities with changes.</p> </blockquote> <h4>Conflict Resolution Preview Access</h4> <blockquote><p><strong>Given</strong> the user is viewing the "Review Changes" panel and there are auto-save entries with detected conflicts</p> <p><strong>When</strong> the user clicks on the "Resolve X conflicts" button in the "Conflict resolution CTA" section above the list of un-published entities in the "Review Changes" panel</p> <p><strong>Then</strong> the "Conflict resolution UI" opens and it contains the list of all detected conflicts in the order they appear in "Review Changes"</p> <p><strong>And</strong> this UI has controls that allow the user to navigate between the conflicts, view them in side-by-side comparison and resolve each one individually.</p> </blockquote> <h4>Conflict Resolution Access via contextual menu</h4> <blockquote><p><strong>Given</strong> the user is viewing the "Review Changes" panel when conflicts are detected</p> <p><strong>When</strong> the user chooses an entity with a conflict and clicks on "Resolve Conflict" option in the contextual menu this entity entity</p> <p><strong>Then</strong> the "Conflict resolution UI" opens starting the list with the entity chosen by the user</p> <p><strong>And</strong> the rest of the entities with conflicts in the "Conflict resolution UI" list are ordered as they appear in "Review changes" panel.</p> </blockquote> <h4>Conflict Resolution Selection</h4> <blockquote><p><strong>Given</strong> the user is viewing the side-by-side comparison of a an entity with detected conflict in the "Conflict resolution UI"</p> <p><strong>When</strong> the user is prompted to resolve the conflict</p> <p><strong>Then</strong> the user must be presented with two distinct, mutually exclusive options: "Keep Published Version" or "Keep Autosaved Version".</p> </blockquote> <h4>Executing the resolution</h4> <blockquote><p><strong>Given</strong> the user has selected one of the versions in the conflict resolution modal</p> <p><strong>When</strong> the user confirms their selection</p> <p><strong>Then</strong> the conflict is resolved immediately for this and other users in Canvas Ui.</p> </blockquote> <h4>Expected conflict resolution ui behavior</h4> <blockquote><p><strong>Given</strong> the user is using conflict resolution UI</p> <p><strong>When</strong> resolves a conflict</p> <p><strong>Then</strong> Canvas UI is updated immediately to reflect the users action</p> <p><strong>And</strong> all counts of conflicts in Canvas UI are updated</p> <p><strong>And</strong> the entity is no longer marked as having a conflict.</p> </blockquote> <h4>Resolving the conflict by choosing the Autosaved version</h4> <blockquote><p><strong>Given</strong> the user has selected the Autosaved of the version in the conflict resolution modal</p> <p><strong>When</strong> the user confirms their selection by clicking "Resolve Conflict" button in the "Conflict resolution UI"</p> <p><strong>Then</strong> the changes in the Published version are ignored</p> <p><strong>And</strong> the conflict indicator is removed from the "Review Changes" panel for this entity</p> <p><strong>And</strong> the entity can be selected for publishing in the "Review Changes" panel</p> <p><strong>And</strong> the user can publish the changes made to this entity.</p> </blockquote> <h4>Resolving the conflict by choosing the Published version</h4> <blockquote><p><strong>Given</strong> the user has selected one the Published version in the conflict resolution modal</p> <p><strong>When</strong> the user confirms their selection by clicking "Resolve Conflict" button in the "Conflict resolution UI"</p> <p><strong>Then</strong> user made changes to this entity in Canvas are discarded</p> <p><strong>And</strong> the entity is removed from the "Review changes" list</p> <p><strong>And</strong> there are no changes to publish for this entity in Canvas UI.</p> </blockquote> <h4>Side-by-Side Preview Access</h4> <blockquote><p><strong>Given</strong> the user is viewing the "Review Changes" panel</p> <p><strong>When</strong> the user clicks on the "Review selected changes" link at the bottom of "Review Changes" panel</p> <p><strong>Then</strong> a new Canvas UI element opens displaying a side-by-side comparison of the published version and the autosaved version of the first entity selected for publishing</p> <p><strong>And</strong> all the other entities with changes that are selected for publishing are also available for preview in this new UI.</p> <p><strong>And</strong> the Page entities appear in the same order as they appear in the "Review Changes" panel.</p> </blockquote> <h4>Side-by-Side Preview UI navigation</h4> <blockquote><p><strong>Given</strong> the user opens the new Canvas side-by-side review UI panel</p> <p><strong>When</strong> the user wants to review another entity using this UI</p> <p><strong>Then</strong> they can use the pagination in the left bottom corner to see how many entities are available for preview and which entity number they are on</p> <p><strong>And</strong> they can use "previous" and "next" navigation controls of the new "side-by-side review UI" to move between all Page entities with changes selected for publishing.</p> </blockquote> <h4>Side-by-Side Preview Access via contextual menu</h4> <blockquote><p><strong>Given</strong> the user is viewing the "Review Changes" panel</p> <p><strong>When</strong> the user clicks on contextual menu option "Review Changes" of a specific entity selected for publishing</p> <p><strong>Then</strong> a new Canvas UI element opens displaying a side-by-side comparison of the entity user clicked selected via the contextual menu</p> <p><strong>And</strong> this entity is set to be the first item in the pagination of the new side-by-side UI panel</p> <p><strong>And</strong> this new Canvas UI element has controls that allow user to navigate between the entities selected for publishing and view them in side-by-side comparison</p> <p><strong>And</strong> all the rest of Page entities selected for publishing appear in the in the pagination of the new side-by-side UI panel using same order as they appear in the "Review Changes" panel.</p> </blockquote> <h4 id="proposed-resolution-additional-criteria">Additional criteria:</h4> <ol> <li>A conflict in Canvas can occur by any means of updating the underlying entity that Canvas user has updated in their autosave. This includes (but is not limited to) config and content import, CLI commands, editing and saving of the entity by another user via Drupal UI or code. </li><li>If user resolves a conflict in favour of published entity version, then the entry for that entity is removed from the autosave, resulting in nothing to save or publish until user edits that entity again. </li><li>If a user resolves a conflict in the Canvas UI in favour of entity version in autosave: <ul> <li>If the user continues making changes to an entity with a "conflict resolved" flag in autosave via the Canvas UI, their actions will not trigger a new "conflict detection" flag </li><li>After "conflict resolved" flag is added to an entity in autosave, only further changes to published version of the entity can trigger new "conflict detected" flag </li><li>If a user resolves conflict in the Canvas UI in favour of entity version in autosave, we should track which specific entity and specific conflict has been resolved, in case additional conflicts occur after resolving the initial conflict </li></ul> </li><li>If Canvas will be integrated with Workspaces, the expectation is for conflict resolution to continue work as is in the FE part of Canvas and BE part of Canvas should use entity revision id changes for &ldquo;Conflict detection&rdquo; </li><li>When user opens a modal with side by side visual comparison there should be UI element that allows the comparison modal to be expanded to fill the viewport </li><li>Visual side-by-side comparison of changes should work on all screen sizes supported by Canvas </li><li>Visual side-by-side comparison should be reasonably fast when loading side by side comparisons </li><li>Visual side-by-side comparison must be able successfully render visual comparisons of complex pages with deep component trees. </li><li>Entities that can't be meaningfully compared using visual representations should be compared using their text-based representations (for example - global CSS entity) </li></ol> <h3 id="proposed-resolution-canvas-engineering-speak">Canvas Engineering Speak</h3> <h4>Current conflict detection</h4> <p>We have added rudimentary conflict detection as part of <a href="https://www.drupal.org/project/experience_builder/issues/3490565"></a> and <a href="https://www.drupal.org/project/experience_builder/issues/3526907" title="#3526907"></a>.</p> <p>In <a href="https://www.drupal.org/project/experience_builder/issues/3490565">#3490565</a> we put in logic to throw an exception if client didn't send the latest auto-save hash for the entity and regions. This prevents 2 users for editing at the same time and wiping out each others changes.</p> <p>In <a href="https://www.drupal.org/project/experience_builder/issues/3526907" title="#3526907"></a> we expanded this functionality further and added support in Canvas UI enforcing "Canvas does not support concurrent editing" by forcing Canvas users to refresh their page when a conflict in autosave is detected.</p> <p>This does not cover changes to entity that occur due to entity update and save outside of Canvas UI. For example, if user opens Canvas UI and starts editing "Full content template" for the "Article" content type.</p> <h4>What entity changes are needed to detect a conflict:</h4> <ul> <li>Canvas Page (content entity): any change (including a change in the revision ID) is considered to be a conflict. The only exception is the <code>changed</code> field - updating it without updating any other field values (including revision id) will not be detected as a conflict. </li><li>Content Template, Code Component, Region, Pattern (config entities): the hash of all entity properties will be used to detect changes. The only fields exempt from the hash are: <code>status</code> and <code>label</code> properties. </li></ul> <h4>The broad plan:</h4> <ul> <li>Start small - Canvas Page support as MvP, extend to all the other entity types later </li><li>Text-based side-by-side comparison will use JS library with pretty diff </li><li>Visual side-by-side will use modified Canvas preview endpoint </li><li>Visual comparison will only be available for entity types where it makes sense - Pages, Templates, etc. Entities with no meaningful visual preview (global CSS &#129320;) will only use text-based comparison </li><li>Don't break existing conflict detection, only expand, improve and bugfix where required </li><li>Reuse currently existing infrastructure that is proven to work (like preview endpoint) </li><li>Reuse conflict resolution UI to allow reviews of all entities with changes selected for publishing </li></ul> <h4>The detailed plan:</h4> <ol> <li>Make minor <code>auto-save/pending</code> refactor to encapsulate the auto-save entries in a top level <code>data</code> property. </li><li>Add logic to detect and store conflicts that can be resolved in the new UI: <ul> <li>Changes in <code>AutoSaveManager</code> and associated tests </li><li>Start storing <code>original_hash</code> on auto-save entry save, that way we can tell if underlying entity has been updated. Do not send <code>original_hash</code> to client side </li><li>Start detecting, storing and updating <code>conflict</code> property on auto-save entry save. Only send it client side if the conflict is active and only in the relevant `errors` item </li></ul> </li><li>Add HTTP 409 response definition for <code>auto-save/pending</code> endpoint to openapi.yml: <ul> <li>Use <code>errors</code> at the top level to mark which entities contain conflicts </li><li>In the <code>errors</code> add items for each entity with the detected conflict. Mark items we can resolve in UI with new error code and use <code>meta</code> property of the error item to store the <code>conflict</code> object </li><li>The <code>conflict</code> object should have the <code>conflictId</code>, <code>status</code> and <code>timestamp</code> properties </li><li>HTTP 409 response should return both <code>data</code> and <code>errors</code> properties, so client side could know which entities have active conflicts </li></ul> </li><li>Make <code>auto-save/pending</code> controller return HTTP 409 if any active conflicts are found in the list of entries to be returned </li><li>Make sure entity cache tags from auto-save entries are used in caching of the <code>auto-save/pending</code> response </li><li>Return both <code>data</code> and <code>errors</code> properties on HTTP 409, so client side could know which entities have conflicts </li><li>Extend these changes to publishing and ?? endpoints: <ul> <li>Validation during autosave publishing should compare revision ids and a hashes and prevent publishing if a conflict is detected and not previously marked as resolved </li><li>If any unresolved conflicts are detected during publishing, the autosave should be updated with flags identifying entities with unresolved conflicts </li></ul> </li><li>Add an ability to resolve a conflict via HTTP PATCH call to the auto-save API endpoint (or a new dedicated endpoint) with the conflict id and status </li><li>Modify preview endpoint to accept argument that will allow endpoint to return current auto-save version of the preview (default option) or the published version of this entity </li><li>Add new Canvas endpoint for text-based previews: <ul> <li>Config export and default content export representations of entities should be used </li><li>Both published and auto-save entity per request or two separate requests </li><li>Argument listing properties or a dedicated route with hardcoded entity type to property map can be used to filter text-based representation to a subset of fields/properties (for example - "only input values" or "only JS code") </li><li>Text-based comparison will be available to most (if not all) entity types and will be based on default content export representations for content entities and config export versions of config entities </li><li>For some entity types only a subset of their properties could be displayed in text-based side-by-side (<code>Code</code> tab for code components and global css) + full text-based representation in a separate tab </li></ul> </li><li>Emit notifications in the BE on conflict detection (figure out how to only do it once per detected conflict too) with "action" that allows client side to open conflict resolution UI. </li><li>Have fun along the way </li></ol> <h4 id="proposed-resolution-ui-changes">User interface changes</h4> <ul> <li>"Review changes" dropdown in the Canvas UI should display conflicts when they are detected </li><li>New "Visual comparison UI" that allows user to view a side by side comparison of the of updated entity in autosave versus the published version of same entity </li><li>New "Conflict resolution UI" that shows side by side comparison of updated entity in autosave versus the conflicting published entity. This UI should have controls that allow user to select one version, which results in discarding the other </li><li>"Review changes" dropdown in the Canvas UI should have a mechanism to open a modal (or similar) that would contain "Conflict resolution UI" for entities with detected conflicts </li><li>"Review changes" dropdown in the Canvas UI should have a mechanism to open a modal (or similar) that would contain "Visual comparison UI" for updated entities </li></ul> <h3 id="adr">&#128721; ADR</h3> <p>Work in progress</p> <h3 id="designs">&#128994; Designs</h3> <p>All designs made by <a href="https://www.drupal.org/u/mark-dodgson">@mark-dodgson</a> and exported as PNGs from Figma.</p> <h4 id="designs-conflict-resolution">Conflict resolution</h4> <ol> <li>Toast notification with a detected conflict:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Conflict%201%20-%20toast%20notification.png" alt="Figma design showing toast notification with detected conflict in the Canvas UI"> </li> <li>Conflict detected CTA and marked entities in the "Review changes" panel:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Conflict%205%20-%20review%20changes%20dropdown.png" alt='Design for "Review changes" panel when conflicts are detected'> </li> <li>Conflict resolution UI with side-by-side visual comparison:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Conflict%203%20-%20visual%20comparison.png" alt="Design for visual side-by-side comparison in conflict resolution ui"> </li> <li>Conflict resolution UI when no active conflicts detected:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Conflict%204%20-%20no%20conflicts%20state.png" alt="Design for the status page if the user resolves all conflicts or opens the conflict resolution ui via a path when there are no conflicts to resolve"> </li> </ol> <h4 id="designs-review-changes">Review of changes</h4> <ol> <li>New link at the bottom of "Review changes" panel:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Review%201%20-%20new%20link%20in%20review%20changes.png" alt="Design for the updated Review changes dropdown in Canvas UI"> </li> <li>New "Review changes" UI using visual entity representation for side-by-side comparison of the published entity version with the version user has in Canvas auto-save:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Review%202%20-%20visual%20side%20by%20side.png" alt="Design for the visual side-by-side comparison in review changes ui"> </li> <li>New "Review changes" UI using text-based entity representation for side-by-side comparison:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-29/Review%203%20-%20text%20side%20by%20side.png" alt="Design for the text-based side-by-side comparison in review changes ui"> </li> </ol> <h3 id="related-issues">&#128679;&#127959;&#65039; Related issues</h3> <ol> <li><a href="https://www.drupal.org/project/canvas/issues/3520592">[#3520592]</a><strong>[PP-1] Do not allow a user Publish request while there are pending changes to the current layout</strong> - currently describes a bug that still needs be fixed. This (#3587587) issue helps resolving conflicts and adds visual comparison, but fixing #3520592 would fix a bug causing incorrect conflict detection.</li> <li><a href="https://www.drupal.org/project/canvas/issues/3532056">[#3532056]</a><strong>For less conflict errors only validate region auto-save on layout PATCH</strong> - should still be fixed. It will reduce the amount of conflicts that can occur, but won't solve the problem entirely and this issue will only make it easier to handle the conflicts.</li> <li><a href="https://www.drupal.org/project/canvas/issues/3492065">[#3492065]</a><strong>Enable concurrent editing: Replace the postPreview action with atomic equivalents</strong> - should <strong>definitely</strong> still be implemented. It would add groundwork for implementing concurrent editing for multiple Canvas users. This (#3587587) issue intentionally keeps concurrent changes to the same autosave entry out of scope. Me and <a href="https://www.drupal.org/u/bnjmnm">@bnjmnm</a> think both this (#3587587) and #3492065 issues should work fairly well together as they solve related, but different problems. Some refactor or updates might be required, depending on which issue gets finished and merged first.</li> </ol> <h4>&#8252;&#65039; This issue does not make any of the related issues irrelevant or supersedes them &#8252;&#65039;</h4> <h3 id="out-of-scope">&#128683; Out of scope</h3> <ol> <li><strong>Concurrent editing</strong> - "Conflict free concurrent editing" described in <a href="https://www.drupal.org/project/canvas/issues/3492059">[#3492059]</a> or any other changes related to concurrent editing of entities in Canvas UI by multiple users is out of scope for this issue. </li> <li><strong>Pre-existring "rudimentary conflict detection"</strong> - will continue to work as is:<br><br> <img src="https://www.drupal.org/files/issues/2026-04-08/Screenshot%202026-04-08%20at%2012.35.10.png" alt="Screenshot of autosave conflict detection"> </li> <li><strong>Fine-grained conflict detection</strong> - current implementation of conflict detection does not force the user to refresh the page if the following fields or properties are modified: <ul> <li><code>changed</code> field on for Canvas Page entities (or any other entities implementing <code>EntityChangedInterface</code>)</li> <li><code>status</code> and <code>label</code> properties of Canvas config entities</li> </ul> <p> This will continue to work as is, but <strong>will not be expanded</strong> to include any additional fields.</p></li> <li><strong>Updating the version of the entity in the autosave with changes to it's data fields when possible</strong> - Currently If config entity is saved with label and/or status property updates, these values will also be updated in it's respective autosave entry, if one exists. This will continue working as is. Expanding this functionality to other fields or properties will require careful consideration and is out of scope for this ticket.</li> </ol> > Related issue: [Issue #3520592](https://www.drupal.org/node/3520592) > Related issue: [Issue #3532056](https://www.drupal.org/node/3532056) > Related issue: [Issue #3492059](https://www.drupal.org/node/3492059) > Related issue: [Issue #3492065](https://www.drupal.org/node/3492065) > Related issue: [Issue #3553775](https://www.drupal.org/node/3553775) > Related issue: [Issue #3586600](https://www.drupal.org/node/3586600)
task