From c2e74fec58b6fb9603a248cc13bc7c726452975f Mon Sep 17 00:00:00 2001 From: Matias Palma Date: Sat, 18 Apr 2026 22:59:00 -0400 Subject: [PATCH 1/2] fix: normalize paths in monaco model cleanup --- apps/desktop/src/components/EditorArea.tsx | 34 ++++++---------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/apps/desktop/src/components/EditorArea.tsx b/apps/desktop/src/components/EditorArea.tsx index 414a5b4d..cc82a721 100644 --- a/apps/desktop/src/components/EditorArea.tsx +++ b/apps/desktop/src/components/EditorArea.tsx @@ -106,37 +106,21 @@ const EditorArea: React.FC = () => { return () => resizeObserver.disconnect(); }, [openFiles.length]); - // Memory: Clean up Monaco models when files are closed + // Memory: Clean up Monaco models when files are closed. + // Paths are normalized so Windows backslash paths match Monaco's forward-slash URIs. React.useEffect(() => { if (!monacoRef.current) return; - const monaco = monacoRef.current; - const openPaths = new Set(openFiles.map(f => f.path)); - - // Get all current models in Monaco - const models = monaco.editor.getModels(); - - models.forEach((model: editor.ITextModel) => { - const modelPath = model.uri.toString(); - // If the model path (uri) is not in our openFiles list, dispose it - // Note: Monaco URIs usually look like file:///path/to/file or inmemory://path - // We need to match it against our path. - - const isInternal = modelPath.startsWith("inmemory://"); - if (isInternal) return; // Don't dispose internal models - - // Check if this model corresponds to any open file - const isStillOpen = openFiles.some(f => { - // Handle both absolute paths and URI formats - return f.path === model.uri.fsPath || f.path === model.uri.path || modelPath.endsWith(f.path); - }); - - if (!isStillOpen) { - console.log(`[EditorArea] Disposing unused model: ${modelPath}`); + const normalize = (p: string) => p.replace(/\\/g, "/").toLowerCase(); + const openPaths = new Set(openFiles.map(f => normalize(f.path))); + + monacoRef.current.editor.getModels().forEach((model: editor.ITextModel) => { + if (model.uri.scheme === "inmemory") return; + if (!openPaths.has(normalize(model.uri.fsPath))) { model.dispose(); } }); - }, [openFiles, currentFile]); + }, [openFiles]); const debounceTimer = useRef(null); From 2b2765123ba25b547f2d70649372194d304c9ffe Mon Sep 17 00:00:00 2001 From: Matias Palma Date: Sat, 18 Apr 2026 23:08:19 -0400 Subject: [PATCH 2/2] fix: address review feedback on path normalization and effect deps --- apps/desktop/src/components/EditorArea.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/EditorArea.tsx b/apps/desktop/src/components/EditorArea.tsx index cc82a721..d2eff223 100644 --- a/apps/desktop/src/components/EditorArea.tsx +++ b/apps/desktop/src/components/EditorArea.tsx @@ -107,12 +107,20 @@ const EditorArea: React.FC = () => { }, [openFiles.length]); // Memory: Clean up Monaco models when files are closed. - // Paths are normalized so Windows backslash paths match Monaco's forward-slash URIs. + // Normalize separators so Windows backslash paths match Monaco's forward-slash URIs, + // and only case-fold on Windows/UNC paths to preserve case-sensitivity on Linux. + const openPathKeys = openFiles.map(f => f.path).join("\n"); React.useEffect(() => { if (!monacoRef.current) return; - const normalize = (p: string) => p.replace(/\\/g, "/").toLowerCase(); - const openPaths = new Set(openFiles.map(f => normalize(f.path))); + const normalize = (p: string) => { + const slashed = p.replace(/\\/g, "/"); + const isWindowsPath = /^[A-Za-z]:\//.test(slashed) || slashed.startsWith("//"); + return isWindowsPath ? slashed.toLowerCase() : slashed; + }; + const openPaths = new Set( + openPathKeys.split("\n").filter(Boolean).map(normalize) + ); monacoRef.current.editor.getModels().forEach((model: editor.ITextModel) => { if (model.uri.scheme === "inmemory") return; @@ -120,7 +128,7 @@ const EditorArea: React.FC = () => { model.dispose(); } }); - }, [openFiles]); + }, [openPathKeys]); const debounceTimer = useRef(null);