Skip to content

Fix empty window on first npm start in fresh workspace - #3191

Merged
gcsecsey merged 4 commits into
trunkfrom
gcsecsey/fix-dev-bundle-relaunch
Apr 23, 2026
Merged

Fix empty window on first npm start in fresh workspace#3191
gcsecsey merged 4 commits into
trunkfrom
gcsecsey/fix-dev-bundle-relaunch

Conversation

@gcsecsey

@gcsecsey gcsecsey commented Apr 22, 2026

Copy link
Copy Markdown
Member

Related issues

How AI was used in this PR

Claude Code diagnosed the root cause, proposed the preflight-script approach, and implemented the change. I reviewed the diff and confirmed the fix end-to-end locally.

Proposed Changes

PR #3114 patches node_modules/electron/dist/Electron.app/Contents/Info.plist with a per-workspace CFBundleIdentifier so wp-studio:// deep links don't collide across worktrees in Launch Services. The original implementation did this inside appBoot() and called app.relaunch(); app.exit(0) to make the new ID take effect. That flow does not survive electron-vite dev:

  • npm start spawns Electron via electron-vite dev, which injects dev-only wiring (including ELECTRON_RENDERER_URL) into its child process.
  • On the first launch in a fresh workspace, ensureUniqueDevBundleId() patched the plist and exited with 0.
  • app.relaunch() then spawned a new Electron with the same argv but without electron-vite's dev wiring, so the renderer URL was lost and an empty window appeared.

This PR moves the plist patch out of the Electron main process:

  • Adds a new preflight script scripts/ensure-dev-bundle-id.mjs, that patches the plist before Electron boots. macOS-only, idempotent, no-op on non-darwin or when the plist is already patched.
  • Chains this script in prestart after the CLI build, so npm start runs the patch before electron-vite dev spawns Electron.
  • Removes ensureUniqueDevBundleId() from apps/studio/src/index.ts along with the now-unused execFileSync/createHash imports.

Electron now boots exactly once with the plist already correct. No relaunch, no lost env, no empty window.

Testing Instructions

  • Reset the plist to the default so the preflight has work to do:
    /usr/libexec/PlistBuddy -c 'Set :CFBundleIdentifier com.github.Electron' node_modules/electron/dist/Electron.app/Contents/Info.plist
  • Run npm start
  • Check that the terminal prints Patched Electron.app bundle ID -> com.studio.dev.<hash> during prestart
  • Check that Electron boots only once, and the Studio window renders normally
  • Quit Studio and run npm start a second time
  • Check that preflight produces no output and Studio boots normally
  • Verify the bundle ID is unique per workspace:
    /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' node_modules/electron/dist/Electron.app/Contents/Info.plist
  • Verify wp-studio:// deep links still route correctly to this workspace's running instance (the testing steps from Fix stale protocol handler registration on app boot #3114).

Non-macOS: npm start should behave exactly as before; the preflight exits immediately.

first start subsequent starts
CleanShot 2026-04-22 at 17 10 24@2x CleanShot 2026-04-22 at 17 10 09@2x

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
@gcsecsey gcsecsey self-assigned this Apr 22, 2026
@gcsecsey
gcsecsey requested a review from a team April 22, 2026 16:11
@wpmobilebot

wpmobilebot commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing a93bf7a vs trunk

app-size

Metric trunk a93bf7a Diff Change
App Size (Mac) 1482.50 MB 1482.49 MB 0.00 MB ⚪ 0.0%

site-editor

Metric trunk a93bf7a Diff Change
load 1780 ms 1794 ms +14 ms ⚪ 0.0%

site-startup

Metric trunk a93bf7a Diff Change
siteCreation 8088 ms 8113 ms +25 ms ⚪ 0.0%
siteStartup 4942 ms 4950 ms +8 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@fredrikekelund

Copy link
Copy Markdown
Contributor

So, if I have multiple git workspaces, I still can't have multiple Studio instances open at the same time, right?

@fredrikekelund

Copy link
Copy Markdown
Contributor

So the problem is that macOS keeps a cache of plist bundle IDs mapped to binary paths, and some of those paths may be stale because they belong to another git worktree?

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a second to wrap my head around this, but this LGTM 👍 Much better to do this ahead of launch and guarantee that it doesn't run in production

Comment thread scripts/ensure-dev-bundle-id.mjs Outdated
Comment thread scripts/ensure-dev-bundle-id.mjs Outdated
Co-authored-by: Fredrik Rombach Ekelund <fredrik@f26d.dev>
@gcsecsey

gcsecsey commented Apr 23, 2026

Copy link
Copy Markdown
Member Author

So the problem is that macOS keeps a cache of plist bundle IDs mapped to binary paths, and some of those paths may be stale because they belong to another git worktree?

Mostly yes, as far as I could check, if each worktree is registering the same bundle ID, there's no guarantee from Launch Services that it'll route to the latest registration. That's why it's been non-deterministic which worktree gets the protocol redirect.

So, if I have multiple git workspaces, I still can't have multiple Studio instances open at the same time, right?

I haven't considered this angle yet, but I think this change breaks that constraint for dev builds, because we're locking instances based on bundle ID. I'll check how we could still restrict this for dev builds.

@gcsecsey

Copy link
Copy Markdown
Member Author

I haven't considered this angle yet, but I think this change breaks that constraint for dev builds, because we're locking instances based on bundle ID. I'll check how we could still restrict this for dev builds.

I tested and found that the single instance is still enforced, when starting a second instance it quits at apps/studio/src/index.ts:98. When digging deeper into this, I found that Electron’s requestSingleInstanceLock() is keyed on the userData path, not the bundle ID. So even with these changes, we still can't use a second instance. 👍

@gcsecsey
gcsecsey merged commit 16f8a36 into trunk Apr 23, 2026
10 checks passed
@gcsecsey
gcsecsey deleted the gcsecsey/fix-dev-bundle-relaunch branch April 23, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants