Engineering Journal
Schema Editor
Schema Editor

Layer Rename Does Not Persist After the Next Panel Rebuild

2026-06-04

TLDR

Renaming a layer appears to succeed, then reverts on the next panel rebuild. The cause: a MutationObserver was attached to sync data-layer-name with id, but the observer was attached after the id was already changed. The observer fired with the new value and wrote it, then the next rebuild read data-layer-name before the write landed. Fix: update data-layer-name synchronously at rename time, no observer needed.


Symptom

A user double-clicks a layer entry, types a new name, and presses Enter. The panel shows the new name. After the next canvas operation (adding a shape, moving an element), the panel rebuilds and the layer entry reverts to the old name.

The rename appears to succeed for exactly one panel state. On the next rebuild, the old name is back.

Why It Happens

The layer name was stored in two places: the element's id attribute and a data-layer-name attribute. The panel displayed data-layer-name. The rename handler updated id.

To keep them in sync, a MutationObserver was attached to observe id changes and update data-layer-name:

// wrong: observer attached after id is already changed
function renameElement(el, newName) {
    const obs = new MutationObserver(() => {
        el.setAttribute('data-layer-name', el.id.replace(/-/g, ' '));
        obs.disconnect();
    });
    obs.observe(el, { attributes: true, attributeFilter: ['id'] });
    el.id = newName.replace(/\s+/g, '-').toLowerCase(); // id changes here
    // observer fires on next microtask — after setAttribute above runs
}

The observer fires as a microtask after the id assignment. In the same synchronous turn, the panel rebuild may be triggered (by the canvas operation that followed the rename). The rebuild reads data-layer-name, which has not been updated yet because the observer has not fired yet. The rebuild renders the old name.

When the observer fires, it writes the new name. But the panel already rendered with the old name and there is no second rebuild.

The Fix

Update data-layer-name synchronously at rename time. No observer needed:

// correct: synchronous update, no observer
function renameElement(el, newName) {
    const safeId = newName.replace(/\s+/g, '-').toLowerCase();
    el.id = safeId;
    el.setAttribute('data-layer-name', newName); // synchronous
}

The panel reads data-layer-name on rebuild. By the time any rebuild runs, the attribute already holds the correct value.

If you need to observe external id changes (from undo, import, or other sources), use MutationObserver, but handle the rename case synchronously first. Do not rely on the observer for state that you control directly.

How to Prevent It

Never use a MutationObserver to sync two attributes where you control both. If your code changes attribute A and needs attribute B to reflect it, update both in the same synchronous call. Observers are for reacting to changes you did not make.

The Generalizable Lesson

Asynchronous observers (MutationObserver, IntersectionObserver, ResizeObserver) fire after the current synchronous execution completes. If another synchronous operation reads the state they are meant to update before they fire, it reads stale state. For state you control directly, synchronous updates are always preferable to observer-driven sync.

Read this post in the full Engineering Journal →