From 4b307828d71573789d94452e85d60c7265b90a3d Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 12 Feb 2026 10:34:53 +0530 Subject: [PATCH 1/2] fix: race condition in _loadPreview causing stale iframe overwrites _togglePinUrl() calls _loadPreview(true) without awaiting it, so multiple concurrent _loadPreview calls can interleave. When the earlier call's getPreviewDetails() involves slow async I/O (e.g. FileSystem.existsAsync for SVG files on Chrome's virtual filesystem), a newer _loadPreview call can complete first and set the correct iframe URL. The older call then resumes with stale previewDetails and overwrites the iframe, breaking the live preview. Add a generation counter so that after the await, a _loadPreview call detects if a newer call was initiated and bails out, preventing stale results from clobbering the current preview state. Co-Authored-By: Claude Opus 4.6 --- src/extensionsIntegrated/Phoenix-live-preview/main.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index b28c84eaa..1c393d0ef 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -545,7 +545,8 @@ define(function (require, exports, module) { let panel, urlPinned, currentLivePreviewURL = "", - currentPreviewFile = ''; + currentPreviewFile = '', + _loadGeneration = 0; function _blankIframe() { // we have to remove the dom node altog as at time chrome fails to clear workers if we just change @@ -795,8 +796,12 @@ define(function (require, exports, module) { if(!isPreviewLoadable){ return; } + const thisGeneration = ++_loadGeneration; // panel-live-preview-title let previewDetails = await StaticServer.getPreviewDetails(); + if(thisGeneration !== _loadGeneration) { + return; // A newer _loadPreview call has been made; this one is stale + } if(urlPinned && !force) { return; } From 8fe29c8ad4fb10ab9ebcc79b08df18c75d5a2067 Mon Sep 17 00:00:00 2001 From: abose Date: Thu, 12 Feb 2026 11:13:01 +0530 Subject: [PATCH 2/2] fix: intermittent JSON parse error in KeybindingManager integ test on Mac Tauri On Tauri/macOS, native file writes truncate before writing new content. When awaitsFor polls read the keybinding JSON file between truncation and write completion, JSON.parse fails on empty/partial content. Since awaitsFor immediately rejects on any exception rather than retrying, a single partial read kills the test. Wrap all 6 JSON.parse(await _readKeyboardJson(...)) calls in try-catch blocks so parse errors on mid-write reads return false, allowing awaitsFor to retry on the next poll cycle instead of failing the test. --- test/spec/KeybindingManager-integ-test.js | 50 +++++++++++++++++------ 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/test/spec/KeybindingManager-integ-test.js b/test/spec/KeybindingManager-integ-test.js index 64061cc39..e637c0883 100644 --- a/test/spec/KeybindingManager-integ-test.js +++ b/test/spec/KeybindingManager-integ-test.js @@ -98,8 +98,12 @@ define(function (require, exports, module) { await _writeKeyboardJson(KeyBindingManager._getUserKeyMapFilePath()); await KeyBindingManager._loadUserKeyMapImmediate(); await awaitsFor(async ()=>{ - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - return Object.keys(textJson.overrides).length === 0; + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return Object.keys(textJson.overrides).length === 0; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } }, "reset user shortcuts"); await awaitsFor(()=>{ const binding = KeyBindingManager.getKeyBindings(Commands.EDIT_BEAUTIFY_CODE); @@ -203,8 +207,12 @@ define(function (require, exports, module) { testWindow.$(".change-shortcut-dialog .Remove").click(); let existingBinding = KeyBindingManager.getKeyBindings(Commands.EDIT_BEAUTIFY_CODE); await awaitsFor(async ()=>{ - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - return textJson.overrides[existingBinding[0].key] === null; + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return textJson.overrides[existingBinding[0].key] === null; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } }, "command to be removed"); }); @@ -212,8 +220,12 @@ define(function (require, exports, module) { await editShortcut(Commands.EDIT_BEAUTIFY_CODE); keyboardType('F6'); await awaitsFor(async ()=>{ - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - return textJson.overrides['F6'] === Commands.EDIT_BEAUTIFY_CODE; + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return textJson.overrides['F6'] === Commands.EDIT_BEAUTIFY_CODE; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } }, "F6 to be assigned"); }); @@ -221,8 +233,12 @@ define(function (require, exports, module) { await editShortcut(Commands.EDIT_BEAUTIFY_CODE); keyboardType('Shift-F6'); await awaitsFor(async ()=>{ - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - return textJson.overrides['Shift-F6'] === Commands.EDIT_BEAUTIFY_CODE; + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return textJson.overrides['Shift-F6'] === Commands.EDIT_BEAUTIFY_CODE; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } }, "Shift-F6 to be assigned"); }); @@ -246,8 +262,12 @@ define(function (require, exports, module) { expect(testWindow.$(".change-shortcut-dialog .Assign").is(":visible")).toBeTrue(); testWindow.$(".change-shortcut-dialog .Assign").click(); await awaitsFor(async ()=>{ - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - return textJson.overrides['F1'] === Commands.EDIT_BEAUTIFY_CODE; + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return textJson.overrides['F1'] === Commands.EDIT_BEAUTIFY_CODE; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } }, "F1 to be assigned"); }); @@ -262,8 +282,14 @@ define(function (require, exports, module) { expect(testWindow.$(".change-shortcut-dialog .Assign").is(":visible")).toBeTrue(); testWindow.$(".change-shortcut-dialog .Cancel").click(); await awaits(200); - const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); - expect(textJson.overrides).toEql({}); + await awaitsFor(async ()=>{ + try { + const textJson = JSON.parse(await _readKeyboardJson(KeyBindingManager._getUserKeyMapFilePath())); + return Object.keys(textJson.overrides).length === 0; + } catch (e) { + return false; // file may be mid-write; retry on next poll + } + }, "overrides to be empty"); expect(testWindow.$(".change-shortcut-dialog").is(":visible")).toBeFalse(); });