Repository navigation
fix(desktop): let the bootstrap surface stay silent until it has something to report #5412
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,26 @@ const bridge = window.__TAURI__; | |
| const invoke = bridge && bridge.core && bridge.core.invoke; | ||
| const listen = bridge && bridge.event && bridge.event.listen; | ||
|
|
||
| // The shell owns the sequence and its deadline. The page has no deadline of its own: an invoke | ||
| // whose command never answers returns a promise that neither settles nor rejects, and the page | ||
| // then keeps its initial markup forever - the headline still says the run is starting, the | ||
| // checklist is empty, and the only thing on screen is a Retry button with an empty diagnostic. | ||
| // That is indistinguishable from a hung product. Bounding the handshake turns the silence into a | ||
| // failure the page can report and the user can copy. | ||
| const HANDSHAKE_DEADLINE_MS = 5000; | ||
|
|
||
| function withDeadline(work, what) { | ||
| return new Promise((resolve, reject) => { | ||
| const timer = setTimeout(() => { | ||
| reject(new Error('the shell did not answer ' + what + ' within ' + HANDSHAKE_DEADLINE_MS + ' ms')); | ||
| }, HANDSHAKE_DEADLINE_MS); | ||
| Promise.resolve(work).then( | ||
| (value) => { clearTimeout(timer); resolve(value); }, | ||
| (error) => { clearTimeout(timer); reject(error); }, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| const headline = document.querySelector("#headline"); | ||
| const detail = document.querySelector("#detail"); | ||
| const phaseList = document.querySelector("#phases"); | ||
|
|
@@ -120,12 +140,12 @@ async function start() { | |
| return; | ||
| } | ||
| try { | ||
| phases = (await invoke("startup_phases")).filter((phase) => !phase.terminal); | ||
| phases = (await withDeadline(invoke("startup_phases"), "startup_phases")).filter((phase) => !phase.terminal); | ||
| render(null); | ||
| // The listener goes on before the snapshot is read, so a transition landing between the two is | ||
| // delivered rather than lost. | ||
| await listen("startup-phase", (event) => apply(event.payload)); | ||
| apply(await invoke("startup_snapshot")); | ||
| await withDeadline(listen("startup-phase", (event) => apply(event.payload)), "the startup-phase subscription"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,190p' desktop/ui/main.js
sed -n '190,270p' desktop/ui/main.js
sed -n '180,250p' tests/clients/desktop-startup-surface.test.ts
rg -n '"`@tauri-apps/api`|listen\(' desktop package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/nullRepository: lidge-jun/opencodex Length of output: 8616 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- candidate package files ---'
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json|bun\.lockb?|Cargo\.toml|Cargo\.lock)$' || true
printf '%s\n' '--- package metadata ---'
for f in package.json desktop/package.json; do
if [ -f "$f" ]; then
echo "### $f"
cat -n "$f"
fi
done
printf '%s\n' '--- event/startup references ---'
rg -n -C 4 'startup-phase|startup_snapshot|startup_phases|listen\s*\(|unlisten|withDeadline|reportPageFailure|function apply|apply\s*=' --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .
printf '%s\n' '--- relevant test files ---'
git ls-files 'tests/**' | rg 'desktop|startup|main\.js' | head -80Repository: lidge-jun/opencodex Length of output: 41452 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- Tauri dependency ---'
cat -n desktop/src-tauri/Cargo.toml
printf '%s\n' '--- startup producer ---'
cat -n desktop/src-tauri/src/startup.rs
printf '%s\n' '--- Rust app lifecycle around startup ---'
sed -n '1,230p' desktop/src-tauri/src/lib.rs | cat -n
printf '%s\n' '--- desktop startup tests ---'
sed -n '1,260p' tests/clients/desktop-startup-surface.test.ts | cat -n
printf '%s\n' '--- structure guidance ---'
cat -n structure/desktop-shell.mdRepository: lidge-jun/opencodex Length of output: 42494 🌐 Web query:
💡 Result: <source_evidence> Citations:
🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- startup reporting and terminal paths ---'
sed -n '520,735p' desktop/src-tauri/src/startup.rs | cat -n
printf '%s\n' '--- exact main.js target ---'
sed -n '25,155p' desktop/ui/main.js | cat -nRepository: lidge-jun/opencodex Length of output: 15270 Clean up a startup listener that outlives its deadline.
Disable the callback when startup fails, and call the unlisten function when the registration resolves. Suggested fix- await withDeadline(listen("startup-phase", (event) => apply(event.payload)), "the startup-phase subscription");
- apply(await withDeadline(invoke("startup_snapshot"), "startup_snapshot"));
+ let acceptStartupEvents = true;
+ const subscription = listen("startup-phase", (event) => {
+ if (acceptStartupEvents) apply(event.payload);
+ });
+ await withDeadline(subscription, "the startup-phase subscription");
+ apply(await withDeadline(invoke("startup_snapshot"), "startup_snapshot"));
} catch (error) {
+ acceptStartupEvents = false;
+ void subscription.then((unlisten) => unlisten()).catch(() => {});
reportPageFailure("The startup surface could not reach the shell.", error);
}🤖 Prompt for AI Agents |
||
| apply(await withDeadline(invoke("startup_snapshot"), "startup_snapshot")); | ||
| } catch (error) { | ||
| reportPageFailure("The startup surface could not reach the shell.", error); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
startup_phasestakes longer than five seconds, this new deadline exitsstart()before the event listener and snapshot request are installed. The resulting error surface enables Retry, but that button only invokesretry_startup; it never callsstart()again and remains disabled after a successful invoke. If the bridge recovers and the retried runtime still fails, its events and diagnostic are therefore missed and the page stays on the stale handshake error with no usable retry. Have Retry re-establish the page handshake, or otherwise restore the listener and snapshot before restarting the shell sequence.Useful? React with 👍 / 👎.