diff --git a/src/renderer/main/modals/monaco-session.store.ts b/src/renderer/main/modals/monaco-session.store.ts
new file mode 100644
index 000000000..a3b36a6af
--- /dev/null
+++ b/src/renderer/main/modals/monaco-session.store.ts
@@ -0,0 +1,20 @@
+import { writable } from "svelte/store";
+import type { GridAction } from "../../runtime/runtime";
+
+// Tracks the currently-open Monaco code editor so that opening a different
+// code block can decide whether it is safe to auto-close-and-replace it
+// (issue #1390, part 1). Only one Monaco editor can be open at a time.
+export type MonacoSession = {
+ // Unique per editor instance, used to guard the onDestroy cleanup against
+ // a stale instance clearing the session after a newer one registered.
+ token: object;
+ // The action currently being edited.
+ action: GridAction;
+ // True while the editor has unsaved changes or an error ("needs committing").
+ // Kept live by the editor so subscribers react as the user types.
+ dirty: boolean;
+ // Closes the editor's modal window.
+ close: () => void;
+};
+
+export const activeMonacoSession = writable(null);
diff --git a/src/renderer/main/panels/FileManager/FileManager.svelte b/src/renderer/main/panels/FileManager/FileManager.svelte
index 26233f406..a75ea721c 100644
--- a/src/renderer/main/panels/FileManager/FileManager.svelte
+++ b/src/renderer/main/panels/FileManager/FileManager.svelte
@@ -226,6 +226,17 @@
let monacoElement: HTMLElement;
let editor: MonacoEditor.CustomCodeEditor;
+ let saveButton: HTMLElement;
+
+ // Ctrl/Cmd+S saves the file by triggering the Save button, which no-ops on
+ // its own when disabled. Scoped to the editor section's subtree.
+ function handleKeydown(e: KeyboardEvent) {
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") {
+ e.preventDefault();
+ e.stopPropagation();
+ saveButton?.querySelector("button")?.click();
+ }
+ }
const languageOptions = [
{ title: "Plain Text", value: "plaintext" },
@@ -342,10 +353,16 @@
rawContent = assembled;
selectedLanguage = detectLanguage(entry);
- fileContent =
- selectedLanguage === "lua"
- ? GridScript.expandScript(rawContent)
- : rawContent;
+ try {
+ fileContent =
+ selectedLanguage === "lua"
+ ? GridScript.expandScript(rawContent)
+ : rawContent;
+ luaSyntaxError = null;
+ } catch (e) {
+ fileContent = rawContent;
+ luaSyntaxError = String(e);
+ }
savedContent = fileContent;
editor?.setValue(fileContent ?? "");
} catch (e) {
@@ -673,7 +690,11 @@
diff --git a/src/renderer/main/user-interface/cursor-log/LogStream.store.js b/src/renderer/main/user-interface/cursor-log/LogStream.store.js
index 77ca6cb33..2e8771380 100644
--- a/src/renderer/main/user-interface/cursor-log/LogStream.store.js
+++ b/src/renderer/main/user-interface/cursor-log/LogStream.store.js
@@ -66,9 +66,18 @@ function createLogStream() {
}
});
+ // Force the log stream (and the underlying logger) back to an empty state,
+ // cancelling any pending auto-clear. Used to get rid of "bleeding" toasts
+ // that would otherwise linger for up to 5s and overlay later interactions.
+ function reset() {
+ clearTimeout(logClearTimeout);
+ clearLogs(true);
+ }
+
return {
...logStream,
dismissLog: dismissLog,
enableTimeout: enableTimeout,
+ reset: reset,
};
}