repl: ⏸ pause button — manual snapshot mid-eval without leaving the tab
Adds a tabbar button that signals the SAB pause atomic, waits for
the eval to unwind via lisp_error/LispErr("paused"), then drops
into saveCheckpoint so the resulting snapshot lands in the visible
chip strip with a user-chosen name. Workers stay alive so the next
input runs immediately — this is the "I want to inspect/save state
but keep working" path, distinct from autoPauseTab's
"I'm leaving this tab" flow.
The button enables alongside cancel whenever an eval is running and
SAB is available; on plain http.server (no COOP/COEP) it stays
disabled and an alert points at make serve-repl. Asm tier gets a
"not supported on asm tier yet" alert since the WAT interpreter
has no in-eval poll site.
Tabbar grid expanded from 8 to 9 columns; comment updated to track.
This commit is contained in:
parent
6bcc20cb27
commit
b24af478bf
9 changed files with 111 additions and 12 deletions
|
|
@ -84,6 +84,7 @@
|
|||
<span class="spacer"></span>
|
||||
<button id="reset-tier" class="ghost" title="reboot the worker for the current tab — state lost">↻ reboot tier</button>
|
||||
<button id="clear-log" class="ghost" title="clear the transcript">clear log</button>
|
||||
<button id="pause" class="ghost" disabled title="pause the running eval and snapshot its state into the portal vault — resume via the chip">⏸ pause</button>
|
||||
<button id="cancel" class="ghost" disabled>cancel</button>
|
||||
<button id="lock" class="ghost" title="lock vault — passes back to lock screen">lock</button>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
<span class="spacer"></span>
|
||||
<button id="reset-tier" class="ghost" title="reboot the worker for the current tab — state lost">↻ reboot tier</button>
|
||||
<button id="clear-log" class="ghost" title="clear the transcript">clear log</button>
|
||||
<button id="pause" class="ghost" disabled title="pause the running eval and snapshot its state into the portal vault — resume via the chip">⏸ pause</button>
|
||||
<button id="cancel" class="ghost" disabled>cancel</button>
|
||||
<button id="lock" class="ghost" title="lock vault — passes back to lock screen">lock</button>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
<span class="spacer"></span>
|
||||
<button id="reset-tier" class="ghost" title="reboot the worker for the current tab — state lost">↻ reboot tier</button>
|
||||
<button id="clear-log" class="ghost" title="clear the transcript">clear log</button>
|
||||
<button id="pause" class="ghost" disabled title="pause the running eval and snapshot its state into the portal vault — resume via the chip">⏸ pause</button>
|
||||
<button id="cancel" class="ghost" disabled>cancel</button>
|
||||
<button id="lock" class="ghost" title="lock vault — passes back to lock screen">lock</button>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue