diff --git a/wasm/dist-repl/repl.js b/wasm/dist-repl/repl.js index 1ab7f7f..0c2de3c 100644 --- a/wasm/dist-repl/repl.js +++ b/wasm/dist-repl/repl.js @@ -264,7 +264,14 @@ async function autoPauseTab(tab) { } if (activeInput) { - tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() }; + // Asm tier has no SAB poll site, so we can't snapshot env mid-eval. + // Fall back to replay mode: every successful prior input gets + // re-eval'd on resume to rebuild the env, then the active input + // is sent fresh. Same pattern the manual asm portal-save uses. + const replayInputs = (tier === "asm") + ? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input) + : null; + tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() }; } // Terminate the worker(s) so the heap is reclaimed while the tab @@ -284,8 +291,21 @@ async function autoResumeTab(tab) { tab.tier = ap.tier; tierSelectEl.value = ap.tier; - if (ap.blob) { - rebootTier(tab.id, ap.tier); + rebootTier(tab.id, ap.tier); + + if (ap.replayInputs && ap.replayInputs.length) { + // Asm tier replay path — re-eval every successful prior input + // in order so the env is reconstructed before sendInput fires + // the active one. Errors mid-replay are swallowed; restoring + // from a transcript that defined-on-error is the user's call. + for (const src of ap.replayInputs) { + try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ } + } + tab.transcript.push({ + input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`, + results: [{ tier: ap.tier, output: "#t" }], kind: "ok", + }); + } else if (ap.blob) { // Round-trip through the tier so portal-load! has the blob // to read. The bare 'init eval forces the tier to bootstrap // before we push the blob into MEMFS. diff --git a/wasm/repl/repl.js b/wasm/repl/repl.js index 1ab7f7f..0c2de3c 100644 --- a/wasm/repl/repl.js +++ b/wasm/repl/repl.js @@ -264,7 +264,14 @@ async function autoPauseTab(tab) { } if (activeInput) { - tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() }; + // Asm tier has no SAB poll site, so we can't snapshot env mid-eval. + // Fall back to replay mode: every successful prior input gets + // re-eval'd on resume to rebuild the env, then the active input + // is sent fresh. Same pattern the manual asm portal-save uses. + const replayInputs = (tier === "asm") + ? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input) + : null; + tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() }; } // Terminate the worker(s) so the heap is reclaimed while the tab @@ -284,8 +291,21 @@ async function autoResumeTab(tab) { tab.tier = ap.tier; tierSelectEl.value = ap.tier; - if (ap.blob) { - rebootTier(tab.id, ap.tier); + rebootTier(tab.id, ap.tier); + + if (ap.replayInputs && ap.replayInputs.length) { + // Asm tier replay path — re-eval every successful prior input + // in order so the env is reconstructed before sendInput fires + // the active one. Errors mid-replay are swallowed; restoring + // from a transcript that defined-on-error is the user's call. + for (const src of ap.replayInputs) { + try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ } + } + tab.transcript.push({ + input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`, + results: [{ tier: ap.tier, output: "#t" }], kind: "ok", + }); + } else if (ap.blob) { // Round-trip through the tier so portal-load! has the blob // to read. The bare 'init eval forces the tier to bootstrap // before we push the blob into MEMFS. diff --git a/www/repl/repl.js b/www/repl/repl.js index 1ab7f7f..0c2de3c 100644 --- a/www/repl/repl.js +++ b/www/repl/repl.js @@ -264,7 +264,14 @@ async function autoPauseTab(tab) { } if (activeInput) { - tab.autoPause = { tier, blob, inputSrc: activeInput, savedAt: Date.now() }; + // Asm tier has no SAB poll site, so we can't snapshot env mid-eval. + // Fall back to replay mode: every successful prior input gets + // re-eval'd on resume to rebuild the env, then the active input + // is sent fresh. Same pattern the manual asm portal-save uses. + const replayInputs = (tier === "asm") + ? tab.transcript.filter((e) => e.kind !== "error" && e.input !== activeInput).map((e) => e.input) + : null; + tab.autoPause = { tier, blob, replayInputs, inputSrc: activeInput, savedAt: Date.now() }; } // Terminate the worker(s) so the heap is reclaimed while the tab @@ -284,8 +291,21 @@ async function autoResumeTab(tab) { tab.tier = ap.tier; tierSelectEl.value = ap.tier; - if (ap.blob) { - rebootTier(tab.id, ap.tier); + rebootTier(tab.id, ap.tier); + + if (ap.replayInputs && ap.replayInputs.length) { + // Asm tier replay path — re-eval every successful prior input + // in order so the env is reconstructed before sendInput fires + // the active one. Errors mid-replay are swallowed; restoring + // from a transcript that defined-on-error is the user's call. + for (const src of ap.replayInputs) { + try { await evalInTier(tab.id, ap.tier, src); } catch (e) { /* keep going */ } + } + tab.transcript.push({ + input: `; resumed asm tier — replayed ${ap.replayInputs.length} prior inputs`, + results: [{ tier: ap.tier, output: "#t" }], kind: "ok", + }); + } else if (ap.blob) { // Round-trip through the tier so portal-load! has the blob // to read. The bare 'init eval forces the tier to bootstrap // before we push the blob into MEMFS.