Reviewed & tested by the community
Project:
Display Builder
Version:
1.0.x-dev
Component:
Code
Priority:
Normal
Category:
Bug report
Assigned:
Reporter:
Created:
22 Sep 2026 at 13:22 UTC
Updated:
29 Oct 2026 at 13:52 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
mogtofu33 commentedComment #4
mogtofu33 commentedResult
Implemented on the issue branch
3625017-undo-redo-lanquage, for review.The proposed resolution was incomplete. Undo "as a new revision" leaves no revision above the default one, so redo has nothing to go forward to. It needed a small model, and a new field.
The model
revision_translation_affectedfor that language. A text edit is a step of its own language. A structural edit is a step of every language, since the structure is shared.history_step.history_stepon every translation it changes, inInstanceStorage::doPreSave().Changes
history_stepon the instance, installed bydisplay_builder_update_11107(). It changes stored data, so it belongs in beta9, see #3624068: Remove the beta update hooks at RC1, with 1.0.0-beta10 as the required stepping stone.HistoryInterface::getPresent(): the step the language shows. The History log uses it for its present row, which showed the last revision whatever language made it.setNewPresent()with the hash check disabled, on identical data, does that. No production caller does.Undo in a translation
The synchronized
sourcesfield makes the default language the authority on structure: a translation reads the default structure, with its own texts on top. A structural edit made in the default language leaves the stored rows of the translation as they were. So when German undoes a node added in English, the field cascades nothing: the stored German rows did not change.InstanceStoragehandles it. For an undo in a translation, it gives the default language the structure of the target step with its own texts, throughSourceValueList::buildTranslationsMap()andSourceTree::applyTranslations(). That mirrors the privateSourceValueList::cascadeStructureToDefault()of UI Patterns. A public method for it in UI Patterns would replace those lines.Tests
InstanceHistoryTranslationTest: the steps to reproduce of the summary, undo and redo over several steps of one language while the other keeps its edits, an edit after an undo dropping the future of its language only, a structural undo from each language, the present step, and the History log.For review
Comment #5
pdureau commentedCode review
OK, I guess. There are some surprising stuff, especially the
history_stepinteger base field in Instance entity, but I have nothing else to propose. Just smelling something I hope we will not regret later.Feature review
This "undo" step, works as described in comment #4, but is very surprising and is subverting the concept of undo/redo.
Maybe it is the cost to pay to have something working with tarnslations, but I have noticed than we have the same "undo" step when doing undo in a non translated display.
I don't know yet, I am still confused about such a choice. I may need time to process.
Jean, are you the one asking the "For review" questions or is it Claude? If it is the latter, what would be your answers/positions?
Comment #6
mogtofu33 commentedThanks!
The "Undo" step in the log was a bug at the history cap, now fixed. Undo and redo never show in the log, translated display or not. The cap is now 20 steps per language. Retest on a fresh page layout: one already hit by the bug may lose its older steps.
On
history_step: core reverts a translation the same way, with a new revision that merges that language from the old revision with the others from the default one, seeContentEntityStorageBase::createRevision(). Core has no redo though. Redo needs to know which step an undo revision stands for, per language, and that is the field. It has no getter, so it can change later without breaking contrib.Displays without translation share the code path on purpose: a display can get its first translation anytime. Nothing differs for the editor.
The model, by example: German edits a text, English adds a component, German clicks undo. Here undo removes the component in both languages, then a second undo reverts the German text. With "a step belongs to the language it was made in", that undo would revert the German text by loading a revision older than the component, so the component would silently disappear in both languages.
Claude wrote the questions. My answers:
Comment #7
pdureau commentedReview in progress...
Check the different revisions logs logic (as visible in the island dropdown):
Careful with publishing, it seems it changes what is shown.
There are still "undo" steps...
Is this review the opportunity to restore the "Language" col which has been removed from #3555110: Symmetric translation?
Comment #8
pdureau commentedDiscussed with Jean during last weekly: if the strange current beahviour is the "normal" Druapl behaviour when an entity is both revisionable and translatable, let's keep like that for now and postpone this ticket post 1.0.0.
If we introduced this strange behaviour, let's address it ASAP.
Comment #9
pdureau commentedOpening a new branch to test the "normal" Drupal Core behavior for translatable revisionable entities.
Comment #10
pdureau commentedIn Drupal 11, revisions belong to the entity, not to individual translations. There’s no separate revision ID per language. All translations share one revision history. So, saving any translation with “Create new revision” enabled creates a new revision for the whole entity.
However, each translation row in a revision has a
revision_translation_affectedflag. It’s TRUE only for the languages whose values actually changed in that save, and this is introducing interesting stuff in Revision UI:Let's check how it works and how we can leverage this.
Tested with both:
Taxonomy term, Tag vocabulary
I have also hooked

VersionHistoryControllerto show if a translation is the only one "affected" or not:It is a temporary generic alteration for now, but we may need than in Display Builder Revision Log.
/taxonomy/term/1/revisions/fr/taxonomy/term/1/revisionsInvalidArgumentException: Invalid translation language (fr) specified.English affectedEnglish affectedEnglish affectedFrench affectedLet's check revert now:
/taxonomy/term/1/revisions/fr/taxonomy/term/1/revisionsEnglish affectedFrench affectedFrench affectedSo, Core revisions are weird, indeed:
But there are interesting stuff to explore.
Display Builder instance
Adding this to check directly in Drupal UI (without our specific stuff):
Our revisions management is very different from Core:
HistoryButtonshows revisions of all languagesComment #12
pdureau commentedHere is the proposal : https://git.drupalcode.org/project/display_builder/-/merge_requests/488
Finally, I am proposing to assume we have 2 big differences with Drupal Core revision management (while being closer to the Core Content Moderation" module):
Assuming this, we just need 2 changes to fix our revisions management:
If you are OK with the current MR to review, I can add an extra commit to display the Language col only when there are translations, in order to not make the UI too busy.
Comment #13
pdureau commentedbeta9 has been released. Changing the tag.
Comment #14
pdureau commentedRebased. Open to anybody to review.
Comment #15
csakiistvanComment #16
csakiistvan✅ Tested and works — applied MR !488, rebuilt caches, then on a fresh page layout edited a Textfield in EN, FR, EN again and clicked undo from
/fr/: the history now shows a Language column, and the FR-only step keepsrevision_translation_affectedNULL for EN after undo, where without the fix it flipped to 1. The flag bug is fixed; undo from FR still reverts the EN edit, as proposed with MR !488.Prerequisites
content_translationandconfig_translationenabled; a new page layout duplicated fromtest_empty.SELECT revision, langcode, revision_translation_affected FROM display_builder_instance_field_revision.Actual results
/fr/(flags of the FR revision, now default)/fr/(texts)Notes
history_stepfield; MR !488 takes the narrower route from its proposal comment, so the IS needs an update before commit.Code review
3579098, pipeline green;testUndoAndRedoKeepTranslationAffectedFlags()covers the flag regression.Comment #17
pdureau commentedIndeed. The proposal diverges from initial issue summary in purpose. Not having
history_stepis a benefit. Flowing naturally in the revision log is an other.