Skip to content

Commit 454975b

Browse files
authored
Merge pull request #309 from pathsim/fix/gate-run-button-on-toolbox-bootstrap
Gate run button on startup toolbox bootstrap
2 parents 73d18b9 + 0cd964d commit 454975b

1 file changed

Lines changed: 24 additions & 9 deletions

File tree

src/routes/+page.svelte

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,15 @@
560560
}
561561
let pyodideReady = $state(false);
562562
let pyodideLoading = $state(false);
563+
// True once startup `bootstrapToolboxes()` has finished (or failed). The
564+
// engine wheel being up (`pyodideReady`) is not enough: the bootstrap still
565+
// installs the preloaded catalog toolboxes afterwards (and, in engine builds
566+
// that resolve dependencies, the engine base + docutils) via micropip. The
567+
// run button folds this in so it stays in its loading state until that work
568+
// is done, instead of unlocking the moment the wheel is ready.
569+
let bootstrapComplete = $state(false);
570+
let runLoading = $derived(pyodideLoading || !bootstrapComplete);
571+
let runReady = $derived(pyodideReady && bootstrapComplete);
563572
let simRunning = $state(false);
564573
let isRunStarting = false; // Synchronous flag to prevent race conditions
565574
let isContinuing = false; // Synchronous flag to prevent rapid continue calls
@@ -592,10 +601,16 @@
592601
await autoDetectBackend();
593602
await initBackendFromUrl();
594603
await initPyodide();
604+
statusText = 'Loading toolboxes...';
595605
await bootstrapToolboxes();
606+
statusText = 'Ready';
596607
} catch (e) {
597608
console.error('[startup] backend init failed', e);
598609
throw e;
610+
} finally {
611+
// Unlock the run button even if bootstrap failed — a broken
612+
// toolbox shouldn't leave the button stuck in its loading state.
613+
bootstrapComplete = true;
599614
}
600615
})();
601616
void loadFromUrlParam(backendReady).catch((e) => {
@@ -995,7 +1010,7 @@
9951010
// Run simulation (auto-initializes if needed)
9961011
async function handleRun() {
9971012
// Prevent concurrent simulation runs (synchronous check for rapid key presses)
998-
if (simRunning || isRunStarting || pyodideLoading) return;
1013+
if (simRunning || isRunStarting || runLoading) return;
9991014
10001015
// Set flag before any async operations to prevent race conditions
10011016
isRunStarting = true;
@@ -1331,18 +1346,18 @@
13311346
<Icon name="stop-filled" size={16} />
13321347
</button>
13331348
{:else}
1334-
<div class="run-btn-wrapper" class:loading={pyodideLoading}>
1349+
<div class="run-btn-wrapper" class:loading={runLoading}>
13351350
<button
13361351
class="toolbar-btn run-btn"
1337-
class:active={!pyodideLoading}
1338-
class:loading={pyodideLoading}
1352+
class:active={!runLoading}
1353+
class:loading={runLoading}
13391354
onclick={handleRun}
1340-
disabled={pyodideLoading}
1341-
use:tooltip={{ text: pyodideReady ? "Run" : "Initialize & Run", shortcut: "Ctrl+Enter" }}
1355+
disabled={runLoading}
1356+
use:tooltip={{ text: runReady ? "Run" : "Initialize & Run", shortcut: "Ctrl+Enter" }}
13421357
aria-label="Run"
13431358
data-tour="toolbar-run"
13441359
>
1345-
{#if pyodideLoading}
1360+
{#if runLoading}
13461361
<span class="loading-status">{statusText}</span>
13471362
<span class="spinner"><Icon name="loader" size={16} /></span>
13481363
{:else}
@@ -1353,9 +1368,9 @@
13531368
{/if}
13541369
<button
13551370
class="toolbar-btn"
1356-
class:active={hasRunSimulation && pyodideReady && !simRunning}
1371+
class:active={hasRunSimulation && runReady && !simRunning}
13571372
onclick={handleContinue}
1358-
disabled={!hasRunSimulation || !pyodideReady || simRunning}
1373+
disabled={!hasRunSimulation || !runReady || simRunning}
13591374
use:tooltip={continueTooltip}
13601375
aria-label="Continue"
13611376
>

0 commit comments

Comments
 (0)