From 6e842a83c9620247838b93f4b4f6e7e661aa3370 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 15 Apr 2026 20:16:21 +0200 Subject: [PATCH] fix(workbench): set SceneHeader text via refs instead of inline mustache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prettier kept reformatting the

/

bodies onto their own indented lines, which contenteditable renders verbatim — the user saw leading/trailing whitespace inside the edit buffer. prettier-ignore only applies to the immediately next node, and the svelte-ignore comment was taking that slot, so the directive never reached the element. Rewire with bind:this + two $effects that set textContent whenever the scene prop changes and the element isn't currently focused. Side benefit: a scene name synced from another device now updates the header live without interrupting an in-progress edit. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../workbench/scenes/SceneHeader.svelte | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) 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}