diff --git a/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneHeader.svelte b/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneHeader.svelte index 6756a5de6..0b97c12de 100644 --- a/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneHeader.svelte +++ b/apps/mana/apps/web/src/lib/components/workbench/scenes/SceneHeader.svelte @@ -17,10 +17,29 @@ const { scene }: Props = $props(); - // Helpers for the contenteditable flow. We don't bind:textContent - // because Svelte's bindings re-render while the user is typing and - // would fight the caret. Instead we read textContent on blur / key - // events and commit once. + // We avoid inline mustache interpolation inside the contenteditable + // elements because Prettier reformats the template and leaves + // literal leading/trailing whitespace inside the element — which + // contenteditable preserves as part of its edit buffer. Instead, + // we bind element refs and set textContent via $effect whenever the + // scene value changes and the user isn't actively editing. This + // also lets external updates (e.g. a rename synced from another + // device) refresh the visible text without fighting the caret. + let nameEl = $state(null); + let descEl = $state(null); + + $effect(() => { + if (!nameEl || !scene) return; + if (document.activeElement === nameEl) return; + if (nameEl.textContent !== scene.name) nameEl.textContent = scene.name; + }); + + $effect(() => { + if (!descEl || !scene) return; + if (document.activeElement === descEl) return; + const next = scene.description ?? ''; + if (descEl.textContent !== next) descEl.textContent = next; + }); function commitName(el: HTMLElement, current: string) { const next = (el.textContent ?? '').trim(); @@ -75,9 +94,9 @@ {#if scene}
-

commitName(e.currentTarget, scene.name)} - > - {scene.name} -

- + >

commitDescription(e.currentTarget, scene.description ?? '')} - > - {scene.description ?? ''} -

+ >

{/if}