feat(desktop): optimize cold development startup - #42722
Merged
Merged
Conversation
Hona
force-pushed
the
desktop-devex
branch
3 times, most recently
from
August 20, 2026 06:22
65dbb31 to
8afbec1
Compare
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.
Cutting cold desktop startup from 17.4 seconds to 6.7 seconds
bun dev:desktoporiginally took a median of 17,422 ms from command submission to a visible, actionable Home page. After rebasing the experiment onto current V2 and adapting it to the modular desktop architecture, the current five-run fresh-profile median is 6,670 ms.That removes 10,753 ms, a 61.7% reduction, without reusing desktop build output, the desktop Vite cache, the desktop profile, the database, service registration, or the service process.
The window also no longer flashes from light to dark. Electron keeps it hidden until the renderer confirms that the persisted or default theme is fully applied.
Measurement contract
bun run bench:devexruns five serial samples of the exactbun dev:desktopcommand.Before every sample, the harness removes desktop build output and
packages/desktop/node_modules/.vite. It creates a fresh desktop profile, database, service state, registration, and process. The standard managed CLI service selects a new ephemeral loopback port. Dependencies, Bun's package cache, and Electron remain installed, matching a fresh worktree afterbun install.A sample completes only after all of these conditions are true:
data-themeanddata-color-schemedid not change after their initial resolved values.The benchmark records raw samples, median, minimum, maximum, median absolute deviation, startup milestones, derived phases, service identity, stdout, stderr, and renderer errors.
Why the old path was slow
The old desktop path serialized build, Electron, local service, WSL setup, window creation, and renderer initialization. Home also pulled much of the session and draft graph into the initial renderer graph.
The original five-run median looked like this:
A diagnostic crawl recorded 729 transforms and 1,725 module resolutions. A static session-route import accounted for another 278 transforms, including terminal, prompt, review, file-tree, timeline, model-dialog, and drag-and-drop code that Home did not need.
Changes
1. Use Vite 8 bundled development
The renderer now uses Vite's in-memory Rolldown development bundle instead of serving the initial graph as hundreds of browser-requested modules.
8.2.16.0.0-beta.12.11.144.3.3@tailwindcss/vite4.3.3electron-vite 5 supports Vite only through version 7. Version 6 beta is still the newest release with Vite 8 support. The config uses
rolldownOptions, keeps the main process as ESM, emits preload as CJS, and preserves nativenode-ptyexternalization.2. Keep session, draft, and File code outside Home
The current
/server/:serverKey/session/:idand/new-sessionroutes are lazy boundaries. The draft route retains current V2 provider ownership withServerProvider,ModelsProvider,LocationProvider, andSessionUIProvider.The session UI File component is lazy and preloads in parallel with either route. Restored session and draft routes preload while the desktop splash is still active, which avoids replacing faster Home startup with a blank first navigation.
3. Start renderer work and the local service concurrently
Electron starts the local managed service immediately after the application lock and final environment resolution, before
app.whenReady, updater setup, IPC setup, and window preparation. This applies to development and packaged builds.The renderer can compile and apply its theme while the service starts. Its existing initialization IPC boundary still prevents Home from using the local server before credentials are ready.
4. Keep WSL off the local critical path
WSL remains a remote-server concern. Local service readiness is published before WSL setup.
The current typed IPC contract includes a WSL initialization boundary. Early renderer WSL calls wait for that boundary, and subscribe/unsubscribe ordering remains safe if a component disposes while initialization is pending. WSL startup failure does not prevent local Home from opening.
5. Keep the standard CLI boundary
Desktop development continues to start the service through the normal CLI entrypoint:
This PR does not add an alternate CLI entrypoint and has no final
packages/cliorpackages/corediff. CLI startup performance remains owned by the CLI team rather than being bypassed by desktop-specific argument parsing and process composition.Electron main is the single service owner. The dev wrapper does not start another contender.
6. Defer code that cannot affect first Home paint
The cold path now defers:
electron-updaterwhen updates are disabled7. Narrow Tailwind discovery
Tailwind scans explicit app, desktop renderer, enterprise, session UI, storybook, and UI source roots. It excludes locale dictionaries, assets, and test files. The paths are resolved to their real
srcdirectories rather than package-root paths that did not exclude anything.Tailwind 4.3.3 still needs a small guard around its bundled-development HMR hook because the upstream fix has not reached a stable release.
8. Remove the duplicate desktop renderer dictionaries
Native desktop strings already live in the app dictionaries and flow through
createDesktopNativeBundle, preload IPC, andnativeTin the main process.The obsolete renderer-only dictionaries and translation target are removed. The developer-only missing-root invariant uses a direct English diagnostic and no longer pulls a second locale graph into desktop startup.
9. Apply persisted theme state before reveal
The theme preload plugin now matches both web
/oc-theme-preload.jsand Electron./oc-theme-preload.jspaths. It runs in Vite'spreHTML phase, so the script is inline and executes before the renderer module.The preload reads the same persisted keys as
ThemeProvider:opencode-theme-id, defaultoc-2opencode-color-scheme, defaultsystemopencode-theme-css-lightopencode-theme-css-darkExplicit light and dark settings override the OS. System mode resolves
prefers-color-schemebefore paint. Custom themes restore their cached mode-specific CSS.Electron now reveals a BrowserWindow only after both
ready-to-showand the renderer's theme-applied signal. The main process also persists the renderer's computed background color for the next native window construction. This prevents the native surface, startup splash, and final app from showing different modes.10. Reduce service discovery latency
Service discovery polls every
100 msinstead of1,000 ms. The attempt count increases from120to1,200, preserving the original approximate 120-second allowance rather than shortening the timeout.Profiling evidence
The follow-up work used CDP tracing, Bun CPU profiling, service-registration probing, and isolated five-run A/B groups.
The initial renderer trace from attachment through Home reported:
The renderer was mostly waiting. Bun's service profile showed the normal source entry loading and initializing Effect, HTTP API schemas/routes, OpenTelemetry, Redis clients, Babel/Solid transform code, TypeScript, and OpenTUI modules. That work remains CLI/server-owned.
The registration probe found only 134 ms between the registration file appearing and the first authenticated health response. The first response was already
200 ready, so there is no multi-second provisional endpoint phase for desktop to exploit safely.Isolated experiments:
The fresh profile made prewarm look safe because a contender won quickly. A persisted-profile run exposed the failure: wrapper prewarm plus Electron main produced four identical CLI contenders, saturated CPU, repeatedly replaced the registration, and took about 33 seconds. Removing wrapper prewarm restored one CLI owner and reached service readiness in 6.57 seconds with the real profile.
OPENCODE_PERFORMANCE_TRACE_DIRnow enables the same CDP trace directly frombench:devexfor future investigations.Current results
Latest cold samples on current V2:
Current median milestones:
The median visible-window-to-actionable-Home interval is 2,642.63 ms. During that interval the correctly themed loading splash remains visible while the single standard CLI service starts. Every final sample kept one stable resolved theme and color scheme through Home readiness.
Verification
upstream/v2ate2d9376614upstream/v2: nopackages/cliorpackages/corechangesgit diff --checkpassedRisks
6.0.0-beta.1remains a prerelease because no stable electron-vite version supports Vite 8 yet.42.3.3. Newer Electron releases were intentionally excluded so this PR does not change the runtime and benchmark variable at the same time.1.4.0; dependency installation and lockfile generation used repository-pinned Bun1.3.14.