Problem/Motivation
Editing an already-inserted component never refreshes its live preview. The widget keeps showing the previous render. The new value is saved correctly, and only becomes visible after saving the node and re-opening the edit form, which is what makes the bug look like a caching problem rather than a plugin one.
Found on SDC Embed 1.1.1. The 1.x HEAD on git.drupalcode.org carries the same code byte for byte, so the bug is present there too.
Steps to reproduce
- Enable SDC Embed, add the "Embed component" button to a text format's toolbar, enable the sdc_embed_filter on that format, and leave
live_preview: TRUEin sdc_embed.settings. - Edit a node using that format and insert a component.
- Double-click the embedded widget (or select it and press the "Embed component" button), change any prop in the dialog, and submit the dialog.
Expected: the widget's preview re-renders with the new prop value.
Actual: the widget keeps showing the previous render.
Root cause
On the edit path InsertSdcEmbedCommand::execute() does not recreate the model node; it calls writer.setAttribute() on the four data-sdc-* attributes of the already-selected drupalSdc element. Because the editing downcast is a plain elementToElement() converter, nothing reconverts the widget, so the only thing that can repaint it is the change:data listener in SdcEmbedEditing::init(). That listener walks differ.getChanges() and looks the element up like this:
if (change.type === 'attribute') { const parent = change.range?.start?.parent; return parent && parent.is('element', 'drupalSdc') ? parent : null; }
That is the wrong end of the range. For an attribute change on an element, the Differ files the change under the element's parent and reports a range whose start sits in that parent, immediately before the element. So range.start.parent is the parent (typically $root) and the element is range.start.nodeAfter.
range.start.parent would only be the element if the attribute had been set on a text node inside it, which cannot happen here, since drupalSdc is registered as isObject/isBlock and holds no text. The condition is therefore never true, _refreshPreview() never runs, and the editing view keeps the stale HTML.
Corroboration
CKEditor 5 itself draws exactly this distinction in createChangeReducer() in ckeditor5-engine's src/conversion/downcasthelpers.ts, which the downcast helpers register on the dispatcher's reduceChanges event:
const i = "attribute" == e.type ? e.range.start.nodeAfter : e.position.parent;Why insert and reload appear to work
Insertion takes the change.type === 'insert' branch, which is correct, and is additionally covered by the setTimeout(() => this._refreshPreview(modelElement), 0) scheduled from the editing downcast converter. Saving and re-opening the node works because the upcast recreates the model element, which runs the editing downcast again and re-schedules that same first preview. Only the in-place attribute update, which is the actual edit flow, is broken.
Proposed resolution
In the comments.
Remaining tasks
Review.
User interface changes
None.
API changes
None.
Data model changes
None.
Issue fork sdc_embed-3620850
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
luissousa21 commentedComment #4
luissousa21 commentedMR !2.
_findSdcElement()readschange.range.start.nodeAfterfor attribute changes instead of.parent._affectsSdcElement()folds into it, since both carried the same wrong expression and always ran as a pair, and thechange.name === 'drupalSdc'test moves onto the insert branch.The editing downcast is left alone on purpose rather than moved to reconversion. The file's header comment says replacing only the
sdc-embed-bodychild is deliberate, to keep the selection and caret and avoid a flicker on every update.Checked on a clean 11.4.6 install with the demo submodule. Before: change a prop and the model holds
"value":22while the widget still shows11. After, it repaints. One dialog submit still fires a single POST to the preview endpoint, so the 250 ms debounce inschedulePreview()already covers the attribute changes.phpcs clean, phpunit 30 tests / 131 assertions green, eslint no errors. The phpstan, stylelint and cspell findings are all pre-existing; this only touches
js/sdc-embed.js.Comment #7
c2h2o5 commented