diff --git a/wasm/dist-repl/index.html b/wasm/dist-repl/index.html
index 1fcec62..9edf37c 100644
--- a/wasm/dist-repl/index.html
+++ b/wasm/dist-repl/index.html
@@ -84,6 +84,7 @@
+
diff --git a/wasm/dist-repl/repl.css b/wasm/dist-repl/repl.css
index 1bbce52..7f83941 100644
--- a/wasm/dist-repl/repl.css
+++ b/wasm/dist-repl/repl.css
@@ -121,10 +121,10 @@ body.repl {
z-index: 40;
display: grid;
/* tabs · new-tab · spacer · heap-pressure (injected by repl.js) ·
- * reboot · clear · cancel · lock — 8 cells. Extend if the toolbar
- * grows; CSS Grid drops trailing children onto a new row, which is
- * how lock used to wrap. */
- grid-template-columns: auto auto 1fr auto auto auto auto auto;
+ * reboot · clear · pause · cancel · lock — 9 cells. Extend if the
+ * toolbar grows; CSS Grid drops trailing children onto a new row,
+ * which is how lock used to wrap. */
+ grid-template-columns: auto auto 1fr auto auto auto auto auto auto;
gap: 0.3rem;
align-items: center;
padding: 0.4rem 0.4rem;
diff --git a/wasm/dist-repl/repl.js b/wasm/dist-repl/repl.js
index 0c2de3c..0391338 100644
--- a/wasm/dist-repl/repl.js
+++ b/wasm/dist-repl/repl.js
@@ -21,6 +21,7 @@ const newTabBtn = document.getElementById("new-tab");
const resetTierBtn = document.getElementById("reset-tier");
const clearLogBtn = document.getElementById("clear-log");
const cancelBtn = document.getElementById("cancel");
+const pauseBtn = document.getElementById("pause");
const lockBtn = document.getElementById("lock");
const replStream = document.getElementById("repl-stream");
@@ -671,6 +672,34 @@ async function importCheckpoint(file) {
saveSoon();
}
+// Manual pause-and-snapshot — the user wants to inspect or save the
+// state of a long-running eval without leaving the tab. Signals the
+// SAB pause flag, waits for the in-flight eval to settle, then runs
+// saveCheckpoint so the snapshot lands in the visible chip strip
+// with a user-chosen name. Workers stay alive so the next input
+// runs immediately.
+async function pauseAndSnapshot() {
+ if (!pauseFlagView) {
+ alert("pause requires COOP/COEP headers — start with `make serve-repl`, or for production set Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp on the deploy");
+ return;
+ }
+ const tab = activeTab();
+ if (!tab) return;
+ const tier = tab.tier === "all" ? "c" : tab.tier;
+ if (tier === "asm") {
+ alert("manual pause not supported on asm tier yet — use cancel + portal-save when the eval is idle");
+ return;
+ }
+ pauseBtn.disabled = true;
+ Atomics.store(pauseFlagView, 0, 1);
+ await waitForPending(tab.id, tier, 5000);
+ Atomics.store(pauseFlagView, 0, 0);
+ // Reset activeInput marker so a follow-up tab switch doesn't try
+ // to "auto-resume" what the user has now explicitly saved.
+ tab.activeInput = null;
+ await saveCheckpoint();
+}
+
function deleteCheckpoint(name) {
const tab = activeTab();
if (!tab || !tab.checkpoints) return;
@@ -973,6 +1002,7 @@ async function sendInput() {
resetHistory();
sendBtn.disabled = true;
cancelBtn.disabled = false;
+ pauseBtn.disabled = !pauseFlagView;
renderAll();
saveSoon();
@@ -1033,6 +1063,7 @@ async function sendInput() {
if (remaining === 0) {
sendBtn.disabled = false;
cancelBtn.disabled = true;
+ pauseBtn.disabled = true;
inputEl.focus();
// Eval finished cleanly — drop the activeInput marker so
// a subsequent tab switch doesn't try to resume something
@@ -1102,6 +1133,7 @@ clearLogBtn.addEventListener("click", () => {
if (tab) { tab.transcript = []; renderAll(); saveSoon(); }
});
cancelBtn.addEventListener("click", cancelAllPendingInActiveTab);
+pauseBtn.addEventListener("click", pauseAndSnapshot);
portalSaveBtn.addEventListener("click", saveCheckpoint);
portalImportBtn.addEventListener("click", () => portalImportFileEl.click());
portalImportFileEl.addEventListener("change", async (e) => {
diff --git a/wasm/repl/index.html b/wasm/repl/index.html
index 1fcec62..9edf37c 100644
--- a/wasm/repl/index.html
+++ b/wasm/repl/index.html
@@ -84,6 +84,7 @@
+
diff --git a/wasm/repl/repl.css b/wasm/repl/repl.css
index 1bbce52..7f83941 100644
--- a/wasm/repl/repl.css
+++ b/wasm/repl/repl.css
@@ -121,10 +121,10 @@ body.repl {
z-index: 40;
display: grid;
/* tabs · new-tab · spacer · heap-pressure (injected by repl.js) ·
- * reboot · clear · cancel · lock — 8 cells. Extend if the toolbar
- * grows; CSS Grid drops trailing children onto a new row, which is
- * how lock used to wrap. */
- grid-template-columns: auto auto 1fr auto auto auto auto auto;
+ * reboot · clear · pause · cancel · lock — 9 cells. Extend if the
+ * toolbar grows; CSS Grid drops trailing children onto a new row,
+ * which is how lock used to wrap. */
+ grid-template-columns: auto auto 1fr auto auto auto auto auto auto;
gap: 0.3rem;
align-items: center;
padding: 0.4rem 0.4rem;
diff --git a/wasm/repl/repl.js b/wasm/repl/repl.js
index 0c2de3c..0391338 100644
--- a/wasm/repl/repl.js
+++ b/wasm/repl/repl.js
@@ -21,6 +21,7 @@ const newTabBtn = document.getElementById("new-tab");
const resetTierBtn = document.getElementById("reset-tier");
const clearLogBtn = document.getElementById("clear-log");
const cancelBtn = document.getElementById("cancel");
+const pauseBtn = document.getElementById("pause");
const lockBtn = document.getElementById("lock");
const replStream = document.getElementById("repl-stream");
@@ -671,6 +672,34 @@ async function importCheckpoint(file) {
saveSoon();
}
+// Manual pause-and-snapshot — the user wants to inspect or save the
+// state of a long-running eval without leaving the tab. Signals the
+// SAB pause flag, waits for the in-flight eval to settle, then runs
+// saveCheckpoint so the snapshot lands in the visible chip strip
+// with a user-chosen name. Workers stay alive so the next input
+// runs immediately.
+async function pauseAndSnapshot() {
+ if (!pauseFlagView) {
+ alert("pause requires COOP/COEP headers — start with `make serve-repl`, or for production set Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp on the deploy");
+ return;
+ }
+ const tab = activeTab();
+ if (!tab) return;
+ const tier = tab.tier === "all" ? "c" : tab.tier;
+ if (tier === "asm") {
+ alert("manual pause not supported on asm tier yet — use cancel + portal-save when the eval is idle");
+ return;
+ }
+ pauseBtn.disabled = true;
+ Atomics.store(pauseFlagView, 0, 1);
+ await waitForPending(tab.id, tier, 5000);
+ Atomics.store(pauseFlagView, 0, 0);
+ // Reset activeInput marker so a follow-up tab switch doesn't try
+ // to "auto-resume" what the user has now explicitly saved.
+ tab.activeInput = null;
+ await saveCheckpoint();
+}
+
function deleteCheckpoint(name) {
const tab = activeTab();
if (!tab || !tab.checkpoints) return;
@@ -973,6 +1002,7 @@ async function sendInput() {
resetHistory();
sendBtn.disabled = true;
cancelBtn.disabled = false;
+ pauseBtn.disabled = !pauseFlagView;
renderAll();
saveSoon();
@@ -1033,6 +1063,7 @@ async function sendInput() {
if (remaining === 0) {
sendBtn.disabled = false;
cancelBtn.disabled = true;
+ pauseBtn.disabled = true;
inputEl.focus();
// Eval finished cleanly — drop the activeInput marker so
// a subsequent tab switch doesn't try to resume something
@@ -1102,6 +1133,7 @@ clearLogBtn.addEventListener("click", () => {
if (tab) { tab.transcript = []; renderAll(); saveSoon(); }
});
cancelBtn.addEventListener("click", cancelAllPendingInActiveTab);
+pauseBtn.addEventListener("click", pauseAndSnapshot);
portalSaveBtn.addEventListener("click", saveCheckpoint);
portalImportBtn.addEventListener("click", () => portalImportFileEl.click());
portalImportFileEl.addEventListener("change", async (e) => {
diff --git a/www/repl/index.html b/www/repl/index.html
index 1fcec62..9edf37c 100644
--- a/www/repl/index.html
+++ b/www/repl/index.html
@@ -84,6 +84,7 @@
+
diff --git a/www/repl/repl.css b/www/repl/repl.css
index 1bbce52..7f83941 100644
--- a/www/repl/repl.css
+++ b/www/repl/repl.css
@@ -121,10 +121,10 @@ body.repl {
z-index: 40;
display: grid;
/* tabs · new-tab · spacer · heap-pressure (injected by repl.js) ·
- * reboot · clear · cancel · lock — 8 cells. Extend if the toolbar
- * grows; CSS Grid drops trailing children onto a new row, which is
- * how lock used to wrap. */
- grid-template-columns: auto auto 1fr auto auto auto auto auto;
+ * reboot · clear · pause · cancel · lock — 9 cells. Extend if the
+ * toolbar grows; CSS Grid drops trailing children onto a new row,
+ * which is how lock used to wrap. */
+ grid-template-columns: auto auto 1fr auto auto auto auto auto auto;
gap: 0.3rem;
align-items: center;
padding: 0.4rem 0.4rem;
diff --git a/www/repl/repl.js b/www/repl/repl.js
index 0c2de3c..0391338 100644
--- a/www/repl/repl.js
+++ b/www/repl/repl.js
@@ -21,6 +21,7 @@ const newTabBtn = document.getElementById("new-tab");
const resetTierBtn = document.getElementById("reset-tier");
const clearLogBtn = document.getElementById("clear-log");
const cancelBtn = document.getElementById("cancel");
+const pauseBtn = document.getElementById("pause");
const lockBtn = document.getElementById("lock");
const replStream = document.getElementById("repl-stream");
@@ -671,6 +672,34 @@ async function importCheckpoint(file) {
saveSoon();
}
+// Manual pause-and-snapshot — the user wants to inspect or save the
+// state of a long-running eval without leaving the tab. Signals the
+// SAB pause flag, waits for the in-flight eval to settle, then runs
+// saveCheckpoint so the snapshot lands in the visible chip strip
+// with a user-chosen name. Workers stay alive so the next input
+// runs immediately.
+async function pauseAndSnapshot() {
+ if (!pauseFlagView) {
+ alert("pause requires COOP/COEP headers — start with `make serve-repl`, or for production set Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp on the deploy");
+ return;
+ }
+ const tab = activeTab();
+ if (!tab) return;
+ const tier = tab.tier === "all" ? "c" : tab.tier;
+ if (tier === "asm") {
+ alert("manual pause not supported on asm tier yet — use cancel + portal-save when the eval is idle");
+ return;
+ }
+ pauseBtn.disabled = true;
+ Atomics.store(pauseFlagView, 0, 1);
+ await waitForPending(tab.id, tier, 5000);
+ Atomics.store(pauseFlagView, 0, 0);
+ // Reset activeInput marker so a follow-up tab switch doesn't try
+ // to "auto-resume" what the user has now explicitly saved.
+ tab.activeInput = null;
+ await saveCheckpoint();
+}
+
function deleteCheckpoint(name) {
const tab = activeTab();
if (!tab || !tab.checkpoints) return;
@@ -973,6 +1002,7 @@ async function sendInput() {
resetHistory();
sendBtn.disabled = true;
cancelBtn.disabled = false;
+ pauseBtn.disabled = !pauseFlagView;
renderAll();
saveSoon();
@@ -1033,6 +1063,7 @@ async function sendInput() {
if (remaining === 0) {
sendBtn.disabled = false;
cancelBtn.disabled = true;
+ pauseBtn.disabled = true;
inputEl.focus();
// Eval finished cleanly — drop the activeInput marker so
// a subsequent tab switch doesn't try to resume something
@@ -1102,6 +1133,7 @@ clearLogBtn.addEventListener("click", () => {
if (tab) { tab.transcript = []; renderAll(); saveSoon(); }
});
cancelBtn.addEventListener("click", cancelAllPendingInActiveTab);
+pauseBtn.addEventListener("click", pauseAndSnapshot);
portalSaveBtn.addEventListener("click", saveCheckpoint);
portalImportBtn.addEventListener("click", () => portalImportFileEl.click());
portalImportFileEl.addEventListener("change", async (e) => {