This repository was archived by the owner on May 13, 2026. It is now read-only.
fix(tauri): close webview startup race + harden dmg-smoke - #771
Merged
Conversation
Closes the long-standing "blank window on launch" bug. Root cause:
src/server/embedded.rs::start_embedded_server_lazy
-> tokio::spawn(server.run()) // returns immediately
-> Ok(handle) // BEFORE actix_web::bind()
So `start_fold_server` in lib.rs sends Ok(handle) on the channel the
moment the runner is *queued* — the listener hasn't bound yet. When the
window builder navigates to http://localhost:{port} a few ms later,
WebKit hits ECONNREFUSED, pins the error page, and never auto-retries.
Symptom: blank window, console says "Failed to load resource. Could not
connect to the server." even though the backend comes up ~3-5s later.
Fix: in src-tauri/src/lib.rs setup(), after we know the server thread
sent Ok(handle), poll TCP connect against 127.0.0.1:{server_port} for
up to 15s before WebviewWindowBuilder. A successful kernel-level
handshake means actix's accept loop is running too. ~25 lines of
std-only code (no new deps).
Also harden tauri-release.yml's dmg-smoke beyond /api/health:
- Assert `GET /` returns the real React shell (has
`<script type="module">`) — catches the class of bug where
build.rs's stub index.html got embedded instead of a real
`npm run build` output. The stub backend would still pass
/api/health (JSON works fine) but the webview opens blank.
- Actually fetch the JS bundle the shell references and assert
200 OK — confirms RustEmbed wiring end-to-end.
Together: race fix is deterministic, smoke catches structural drift.
"This keeps happening" should stop happening.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Summary
Fixes the long-standing blank-window-on-launch bug ("this keeps happening"). Two changes:
1. Close the race in
src-tauri/src/lib.rs(the actual fix)The race chain:
start_embedded_server_lazysendsOkthe instant the spawn happens —actix_web::HttpServer::bindis still pending in the spawned task. The webview navigates a few ms later and races. WebKit doesn't auto-retry onECONNREFUSED— the error page sticks even after the backend comes up 3-5s later.Fix: in setup(), after we have the channel
Ok(handle), pollTcpStream::connect_timeoutagainst127.0.0.1:{server_port}for up to 15s before building the window. A successful TCP handshake means actix's accept loop is processing — by the time the webview opens its connection, the listener wins.~25 lines of
std::net::TcpStream— no new deps. Logs whether the readiness check succeeded so we can spot regressions in user logs.2. Harden
tauri-release.ymldmg-smokeExisting smoke only probes
/api/health(JSON), so it never exercised the webview-loadable path. Adds two assertions:GET /must return the real React shell (presence of<script type=\"module\">). Catches the class of regression where build.rs's stubindex.htmlgot embedded instead of a realnpm run buildoutput — the stub passes/api/health(backend works) but opens the webview to a blank page.Race fix is deterministic; smoke catches structural drift. Together they should make blank windows non-recurring.
Why dmg-smoke didn't catch this before
dmg-smoke launches the binary directly (not via
open -a), and probes the embedded backend's/api/health. It never created a Tauri webview, so it couldn't see the race. The newGET /+ JS-bundle checks add the next-cheapest layer of defense without needing a real webview.Test plan
v0.3.15(git tag -a v0.3.15 -m v0.3.15 main && git push origin v0.3.15).🤖 Generated with Claude Code