From 560b217673a0d2398b3383e9684f410e2a24cdbb Mon Sep 17 00:00:00 2001 From: JuanMa Date: Sat, 8 Aug 2026 23:56:30 +0200 Subject: [PATCH] Let the editor open with a window on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The editor was spawned with `windowsHide: true`, the same option the app uses for its console children, where it stops Windows allocating a visible console for every npm and grunt subprocess. For a GUI application it means something else. The flag fills the new process's STARTUPINFO with "start hidden", and an application that honors that value when it creates its first window starts invisible. VS Code does. The spawn still succeeds, so the app reported the launch as fine and the contributor got no window and no notice — the button did nothing, with nothing to debug. The runners keep the flag: it is applied to them through hide-child-windows.js, which this does not touch. Only the editor loses it, because there the window is the point of the click. Fixes #181 Co-Authored-By: Claude Opus 5 (1M context) --- src/editor-launch.js | 16 +++++++++++----- test/editor-launch.test.cjs | 28 +++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/editor-launch.js b/src/editor-launch.js index 81fa6d5..d298295 100644 --- a/src/editor-launch.js +++ b/src/editor-launch.js @@ -290,9 +290,16 @@ const REFUSAL_REASONS = { // editor that has since been uninstalled, is refused rather than resolved // through an environment that is not there. // -// The spawn options are the ones main.js holds everywhere else it starts a -// child: no shell, hidden on Windows, detached with no stdio so the editor -// outlives the app and cannot block on a pipe nobody reads. +// The spawn options: never a shell, and detached with no stdio so the editor +// outlives the app and cannot block on a pipe nobody reads. `detached` is +// unconditional here, unlike the runners in main.js which set it only off +// Windows — they are killed as a process group, and this child is released +// rather than ever signalled. +// +// No `windowsHide`. That flag fills STARTUPINFO with "start hidden", and a GUI +// application that honors it — VS Code does — launches with its window +// invisible while the spawn still reports success (#181). It is for children +// the app runs to collect their output; here the window is the point. async function openSiteInEditor(sitePath, editorPath, { sites, platform, @@ -321,8 +328,7 @@ async function openSiteInEditor(sitePath, editorPath, { child = spawn(command, args, { detached: true, stdio: 'ignore', - shell: false, - windowsHide: true + shell: false }); } catch (e) { // A synchronous throw is the argument-shape failure only. The one that diff --git a/test/editor-launch.test.cjs b/test/editor-launch.test.cjs index adee77a..6a370b2 100644 --- a/test/editor-launch.test.cjs +++ b/test/editor-launch.test.cjs @@ -390,7 +390,7 @@ test('a refusal is logged on one bounded line', async () => { assert.ok(description.length <= 121); }); -test('the child is detached, shell-free and hidden, and its handle released', async () => { +test('the child is detached and shell-free, and its handle released', async () => { const { calls, options } = launchDeps(); await openSiteInEditor(SITE, EDITOR, options); @@ -398,12 +398,34 @@ test('the child is detached, shell-free and hidden, and its handle released', as assert.deepEqual(calls[0].options, { detached: true, stdio: 'ignore', - shell: false, - windowsHide: true + shell: false }); assert.equal(calls[0].unrefed, true); }); +// `windowsHide` fills the new process's STARTUPINFO with "start hidden", and a +// GUI application that honors it — VS Code does — launches with its window +// invisible while the spawn still reports success, so the app said ok and the +// contributor saw nothing (#181). The flag exists to suppress console windows +// the app's own tooling creates; the editor's window is the point of the click. +test('the editor is not asked to start hidden on Windows', async () => { + const winEditor = 'C:\\Users\\dev\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe'; + const winSite = 'C:\\Users\\dev\\Desktop\\wp'; + const fs = fakeFs({ [winEditor]: 'exe' }); + const { calls, spawn } = recordingSpawn(); + + const result = await openSiteInEditor(winSite, winEditor, { + sites: [winSite], + platform: 'win32', + statPath: fs.statPath, + spawn + }); + + assert.deepEqual(result, { ok: true }); + assert.ok(!('windowsHide' in calls[0].options), + 'a GUI editor must not inherit a hidden first window'); +}); + test('a spawn that throws is reported, not raised at the window', async () => { const { options } = launchDeps({ spawn: () => { throw new TypeError('args must be an array'); }