Repository navigation
perf(desktop): show the first window the moment Electron is ready - #49869
Merged
Merged
Conversation
The entry module awaited the whole main bundle, which held back Electron's ready event; windows were then created only after every layer had built, hidden until the renderer had painted and reported its theme. Users saw nothing for ~900 ms. The entry is now a small module that configures the app, takes the single-instance lock and registers the renderer scheme (all of which must precede ready), then on ready creates and shows the first restored window - bounds from its state file, background from the persisted theme colour - and only then imports the rest of the main process, which adopts that window in restoreWindows() and loads the renderer into it. Starting the import before ready measured worse: the module graph evaluates on the thread Chromium needs to finish initialising. Configuration moves from an Effect in the lifecycle layer to a synchronous configure.ts; the renderer scheme constants move to scheme.ts; frame defaults shared by the early window and windowAppearance live in defaults.ts. The bench reads the early window's shownAt from the adoption log line. Packaged build, warm service, interleaved with the v2 baseline, median of 5: window visible 893 -> 251 ms, first paint 1065 -> 812 ms; the first log line and the shell land ~65-100 ms later because the bundle now starts after the window exists.
Kill the launched app and stop the bench service when a run throws, so a launch that never produced a renderer cannot leave an instance behind that later launches hand off to. Compared builds must bundle the same CLI version (read from opencode-cli.version), otherwise the desktop restarts the service on the mismatch and the comparison is meaningless.
The context menu call lived in configureApplication and was lost when that moved to the entry module. Install it after restoreWindows(), where electron-context-menu attaches to existing and future windows alike, which also keeps its dependency tree off the startup path (#49794).
jinhuang712
pushed a commit
to jinhuang712/opencode
that referenced
this pull request
Sep 26, 2026
divyanshusingh2903
added a commit
to divyanshusingh2903/forge
that referenced
this pull request
Oct 21, 2026
Create the main windows before waiting on the sidecar health check; the renderer already awaits the sidecar credentials over IPC, so it can paint while the server boots. Also run the login-shell environment probe asynchronously so it overlaps Electron startup and window creation instead of blocking the main process. On a dev build the renderer now reaches the server request at ~0.8s instead of ~5.2-6.2s after launch. Window ordering adapted from upstream 5b565e8 (anomalyco#49763) and e1e2efe (anomalyco#49869); the async shell probe is Forge's own.
3 of 6 tasks
divyanshusingh2903
added a commit
to divyanshusingh2903/forge
that referenced
this pull request
Oct 21, 2026
Create the main windows before waiting on the sidecar health check; the renderer already awaits the sidecar credentials over IPC, so it can paint while the server boots. Also run the login-shell environment probe asynchronously so it overlaps Electron startup and window creation instead of blocking the main process. On a dev build the renderer now reaches the server request at ~0.8s instead of ~5.2-6.2s after launch. Window ordering adapted from upstream 5b565e8 (anomalyco#49763) and e1e2efe (anomalyco#49869); the async shell probe is Forge's own. Upstream: anomalyco/opencode@5b565e8923 (anomalyco#49763) Upstream: anomalyco/opencode@e1e2efe8ec (anomalyco#49869)
divyanshusingh2903
added a commit
to divyanshusingh2903/forge
that referenced
this pull request
Oct 21, 2026
Create the main windows before waiting on the sidecar health check; the renderer already awaits the sidecar credentials over IPC, so it can paint while the server boots. Also run the login-shell environment probe asynchronously so it overlaps Electron startup and window creation instead of blocking the main process. On a dev build the renderer now reaches the server request at ~0.8s instead of ~5.2-6.2s after launch. Window ordering adapted from upstream 5b565e8 (anomalyco#49763) and e1e2efe (anomalyco#49869); the async shell probe is Forge's own. Upstream: anomalyco/opencode@5b565e8923 (anomalyco#49763) Upstream: anomalyco/opencode@e1e2efe8ec (anomalyco#49869)
divyanshusingh2903
added a commit
to divyanshusingh2903/forge
that referenced
this pull request
Oct 21, 2026
Create the main windows before waiting on the sidecar health check; the renderer already awaits the sidecar credentials over IPC, so it can paint while the server boots. Also run the login-shell environment probe asynchronously so it overlaps Electron startup and window creation instead of blocking the main process. On a dev build the renderer now reaches the server request at ~0.8s instead of ~5.2-6.2s after launch. Window ordering adapted from upstream 5b565e8 (anomalyco#49763) and e1e2efe (anomalyco#49869); the async shell probe is Forge's own. Upstream: anomalyco/opencode@5b565e8923 (anomalyco#49763) Upstream: anomalyco/opencode@e1e2efe8ec (anomalyco#49869)
divyanshusingh2903
added a commit
to divyanshusingh2903/forge
that referenced
this pull request
Oct 21, 2026
Create the main windows before waiting on the sidecar health check; the renderer already awaits the sidecar credentials over IPC, so it can paint while the server boots. Also run the login-shell environment probe asynchronously so it overlaps Electron startup and window creation instead of blocking the main process. On a dev build the renderer now reaches the server request at ~0.8s instead of ~5.2-6.2s after launch. Window ordering adapted from upstream 5b565e8 (anomalyco#49763) and e1e2efe (anomalyco#49869); the async shell probe is Forge's own. Upstream: anomalyco/opencode@5b565e8923 (anomalyco#49763) Upstream: anomalyco/opencode@e1e2efe8ec (anomalyco#49869) Co-authored-by: Luke Parker <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Users saw nothing for ~900 ms after launch. Three things were serial:
index.tsdidawait import("./desktop")— Electron holdsreadyuntil the entry module finishes, so the whole main bundle evaluated before Chromium was even readyshow: falseuntil the renderer had painted and reported its theme colour over IPC (and the renderer waits for the first-launch check before doing that)flowchart LR subgraph before a[Node ready 60] --> b[main bundle evaluates] --> c[ready ~390] --> d[layers] --> e[window created ~520, hidden] --> f[renderer paints + theme IPC] --> g[shown ~890] end subgraph after h[Node ready 60] --> i[tiny entry: configure, lock, scheme] --> j[ready ~220] --> k[window shown ~250] --> l[import desktop] --> m[layers adopt window, loadURL] --> n[renderer paints into the visible window ~810] endWhat moves where
index.tsis now small:configureApplication()(paths, app id, command-line switches, test root — everything that must precedeready, now synchronous inlifecycle/configure.ts), the single-instance lock,registerRendererScheme()(windows/scheme.ts; the privilege call must precedeready), then onready:createEarlyWindow()and only thenimport("./desktop")windows/early.tscreates the first restored window: id from the persisted window list, bounds from its state file, background from the persisted theme colour, the same frame/preload options aswindowAppearance(shared viawindows/defaults.ts), and shows it. Closing it before adoption quits the app.restoreWindows()adopts it (takeEarlyWindow()): security wiring, registry,loadURLas before;reveal()is already satisfied. New windows opened later keep today's behaviour.readyis worse on every milestone (window 276 → 434 ms) because the module graph evaluates on the thread Chromium needs to finish initialising.electron-context-menumoves out of configuration and installs afterrestoreWindows()(it attaches to existing and future windows), which also keeps its dependency tree off the startup path — supersedes perf(desktop): install the context menu after the first window exists #49794shownAtfrom the adoption log line, since the logger doesn't exist yet when the window is shown.Measured with
bun run bench:startup --compare(interleaved launches, warm service, median of 5, currentv2as baseline):The trade: the frame is on screen 640 ms earlier and the splash paints 250 ms earlier; the main bundle now starts after the window exists, so the shell lands ~100 ms later. Main-thread CPU in the first half second is saturated either way — this changes what the user sees first, not how much work there is. Reducing the bundle and layers (the other PRs in this series) shortens the remaining gap.
Also tried, parked on branch
early-renderer: loading the renderer into the early window before the layers. Renderer process 600 → 259 ms, but the main thread then serves its chunks and the RenderProcessHost while the bundle evaluates: first paint 814 → 778, shell 1,135 → 1,087, idle 1,676 → 1,578, while the first log line slips 457 → 605 and service ready 578 → 856 (bad for cold starts). Not a clear win; separate decision.The
~250 msis close to the platform floor: Node bootstrap completes at ~60 ms, Electron's own JS init and Chromium's main-loop setup take it toreadyat ~220,BrowserWindowis ~30.