From 56ea133366875d8bd13347b5002f4ef351a8e4e6 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 6 Aug 2026 09:49:20 +0200 Subject: [PATCH] fix(frontend): reset the editor content when the script language changes (#10560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Picking a new language seeds the new template into `script.content`, which `ScriptEditor` writes straight into Monaco. That write goes through `onDidChangeModelContent`, which arms the keystroke debounce as if the user had typed — so when the `{#key effectiveLang}` block then tears the editor down, the unmount flush sees a pending timer, reads a stale `code`, and dispatches a change that puts the previous language's template back. The editor kept showing the old content under the new language. `alignCodeWithEditor` now clears the timer its own write armed, restoring the premise the unmount flush is guarded on: a pending timer means Monaco holds something newer than `code`. `ScriptEditor` also takes the change payload instead of re-reading `editorCode`. A destroyed component's `bind:` writes no longer reach the parent, so the re-read returned the value from before the change — which is what actually wrote the old template back, and would equally have made the unmount flush save stale text after real typing. Fixes WIN-2330 Co-authored-by: Claude Opus 5 (1M context) --- frontend/src/lib/components/Editor.svelte | 6 ++++++ frontend/src/lib/components/ScriptEditor.svelte | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index c3125dd9ce..f518632483 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -2203,6 +2203,12 @@ } else { ed.setValue(next) } + // The write above went through `onDidChangeModelContent`, which arms the + // keystroke debounce as if the user had typed. Monaco now holds exactly + // `code`, so there is nothing to flush — and leaving the timer armed makes + // every "is Monaco the newer side?" check (notably the unmount flush) answer + // yes on the strength of our own write. + cancelPendingChanges() } // External `code` prop changes should flow into the Monaco editor. Skip diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index dd06a58cde..8110805179 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -2762,7 +2762,11 @@ awareness={wsProvider?.awareness} on:change={(e) => { if (activeModuleTab === null) { - code = editorCode + // The payload, not `editorCode`: the editor also fires this while it is + // being torn down (the unmount flush), and a destroyed component's + // `bind:` writes no longer reach us — re-reading would take the value + // from before the change and write it back over `code`. + code = e.detail lastSyncedCode = code inferSchema(e.detail) } else {