lumbda/wasm/dist-repl/repl.css
russell@unturf.com b24af478bf
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.
2026-06-15 11:20:57 -04:00

390 lines
11 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* lumbda repl — grid-only layout. Inherits palette + base from style.css. */
body.repl {
/* Plain document flow — no min-height, no overflow. The page scrolls
* naturally when content exceeds the viewport; the prompt-bar uses
* position: sticky so it stays glued to the bottom of the viewport in
* that case, and sits right under the transcript otherwise. */
}
/* ─── Lock screen overlay ──────────────────────────────────────── */
.lock-screen {
position: fixed; inset: 0; z-index: 1000;
display: grid;
place-items: center;
background: rgba(15, 17, 21, 0.85);
backdrop-filter: blur(4px);
}
.lock-card {
background: var(--pane-bg);
border: 1px solid var(--rule);
border-radius: 6px;
padding: 1.5rem 2rem;
max-width: 28rem;
width: 90%;
display: grid;
gap: 0.4rem;
}
.lock-card h2 {
margin: 0 0 0.1rem;
font-family: 'chunkfive', Georgia, serif;
font-size: 1.4rem;
color: var(--green);
font-weight: normal;
}
.lock-card p {
margin: 0;
font-size: 0.85rem;
color: var(--muted);
line-height: 1.5;
}
.lock-card input[type=password] {
width: 100%;
padding: 0.5rem 0.7rem;
margin: 0.4rem 0 0;
background: var(--code-bg);
color: var(--fg);
border: 1px solid var(--rule);
border-radius: 3px;
font-family: var(--mono);
font-size: 0.95rem;
}
.lock-card input[type=password]:focus {
outline: none;
border-color: var(--green);
}
.lock-row {
display: grid;
grid-template-columns: auto auto;
gap: 0.6rem;
margin-top: 0.5rem;
justify-content: start;
}
.lock-row button {
font-family: var(--mono);
font-size: 0.85em; font-weight: 600;
padding: 0.45rem 1.1rem;
border-radius: 3px;
cursor: pointer;
border: 1px solid var(--green);
}
.lock-row .primary { background: var(--green); color: var(--bg); }
.lock-row .secondary { background: transparent; color: var(--green); }
.lock-row .primary:hover { filter: brightness(1.1); }
.lock-row .secondary:hover { background: var(--code-bg); }
.lock-note {
margin: 0.4rem 0 0;
color: var(--err);
font-size: 0.8rem;
min-height: 1.2em;
}
.lock-explain {
margin: 0.4rem 0 0;
font-size: 0.78rem;
color: var(--muted);
}
.lock-explain summary {
cursor: pointer;
color: var(--green);
user-select: none;
}
.lock-explain summary:hover { text-decoration: underline; }
.lock-explain ul {
margin: 0.5rem 0 0;
padding-left: 1.1rem;
display: grid;
gap: 0.3rem;
}
.lock-explain li { line-height: 1.45; }
.lock-explain code {
background: var(--code-bg);
padding: 0 0.2em;
border-radius: 2px;
font-size: 0.92em;
}
.lock-explain a {
color: var(--green);
text-decoration: none;
}
.lock-explain a:hover { text-decoration: underline; }
.lock-explain strong { color: var(--fg); }
/* ─── Tab bar ──────────────────────────────────────────────────── */
.tabbar {
/* Sticks to the top of the viewport so it stays visible after the
* header scrolls away — keeps the active-session controls reachable
* even after a long transcript pushes them off-screen. */
position: sticky;
top: 0;
z-index: 40;
display: grid;
/* tabs · new-tab · spacer · heap-pressure (injected by repl.js) ·
* 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;
margin: 0 0 0.4rem;
background: var(--code-bg);
border-bottom: 1px solid var(--rule);
}
.tabs {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: 0.3rem;
overflow-x: auto;
}
.tab {
background: var(--pane-bg);
border: 1px solid var(--rule);
padding: 0.25rem 0.6rem;
border-radius: 3px 3px 0 0;
font-size: 0.85em;
cursor: pointer;
display: inline-grid;
grid-template-columns: auto auto;
gap: 0.3rem;
align-items: center;
white-space: nowrap;
}
.tab.active {
background: var(--code-bg);
border-bottom-color: var(--code-bg);
color: var(--green);
}
.tab .tab-close {
opacity: 0.5;
cursor: pointer;
padding: 0 0.2rem;
}
.tab .tab-close:hover { opacity: 1; color: var(--err); }
.tabbar .ghost {
background: transparent;
border: 1px solid var(--rule);
color: var(--muted);
font-family: var(--mono);
font-size: 0.8em;
padding: 0.2rem 0.6rem;
border-radius: 3px;
cursor: pointer;
}
.tabbar .ghost:hover:not(:disabled) {
border-color: var(--green); color: var(--green);
}
.tabbar .ghost:disabled { opacity: 0.4; cursor: default; }
.tabbar .heap-pressure {
color: var(--muted);
font-size: 0.72em;
font-family: var(--mono);
white-space: nowrap;
padding: 0 0.4rem;
}
/* Portal bar — REPL save/resume controls. Sits between tabbar and
* transcript. Hidden when the active tier has no portal support yet
* (the asm-wat tier stays grey). */
.portal-bar {
display: grid;
/* label · save · import · scope · chips-row */
grid-template-columns: auto auto auto auto 1fr;
gap: 0.4rem;
align-items: center;
padding: 0.2rem 0.5rem;
background: var(--code-bg);
border-bottom: 1px solid var(--rule);
font-size: 0.78em;
font-family: var(--mono);
}
.portal-bar .portal-label {
color: var(--muted);
}
.portal-bar .portal-chips {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
gap: 0.3rem;
overflow-x: auto;
}
.portal-chips .chip {
display: inline-grid;
/* label · ⇣ download · × delete */
grid-template-columns: auto auto auto;
gap: 0.3rem;
align-items: center;
background: var(--pane-bg);
border: 1px solid var(--rule);
border-radius: 3px;
padding: 0.1rem 0.4rem;
color: var(--green);
cursor: pointer;
white-space: nowrap;
}
.portal-chips .chip:hover { border-color: var(--green); }
.portal-chips .chip-global {
/* Cross-tab snapshots get a muted accent so they read as "borrowed
* from another tab" rather than belonging here. */
border-style: dashed;
opacity: 0.85;
}
.portal-chips .chip .chip-export,
.portal-chips .chip .chip-close {
/* Bumped from 0.85em + muted because fox couldn't find the export
* icon on the chip. Now sized at parity with the label and tinted
* with the chip's accent so the action affordance reads as a
* sibling of the name, not a footnote. */
font-size: 1em;
cursor: pointer;
padding: 0 0.15rem;
}
.portal-chips .chip .chip-export { color: var(--green); }
.portal-chips .chip .chip-export:hover { filter: brightness(1.4); }
.portal-chips .chip .chip-close { color: var(--muted); }
.portal-chips .chip .chip-close:hover { color: var(--err); }
/* ─── Transcript ───────────────────────────────────────────────── */
/* The REPL stream is the page's only scroller. Header stays pinned;
* tabs, transcript, and prompt all flow inside this one scroller so
* fresh sessions show the prompt up near the top and it drifts down
* with each new entry. */
.repl-stream {
background: var(--code-bg);
display: grid;
grid-template-rows: auto auto auto;
align-content: start;
}
.transcript {
/* Same horizontal inset as the active prompt-bar so prior λ> sigils
* line up with the live one. No max-width / margin: auto here — a
* centered transcript on wide viewports would mis-align with the
* left-flush prompt-bar. Long lines wrap inside the entry instead. */
padding: 0.4rem 0.4rem 0;
font-family: var(--mono);
font-size: 0.92em;
line-height: 1.45;
}
.transcript .entry {
margin: 0;
padding: 0;
}
.transcript .entry .prompt-line {
color: var(--green);
white-space: pre-wrap;
word-break: break-word;
}
.transcript .entry .prompt-line .sigil {
display: inline-block;
width: 3ch;
opacity: 0.7;
}
.transcript .entry .tier-output {
display: block;
padding: 0;
color: var(--fg);
}
.transcript .entry .tier-result {
white-space: pre-wrap;
word-break: break-word;
padding-left: 3ch; /* line up under "λ> " */
}
.transcript .entry .tier-meta {
color: var(--muted);
font-size: 0.78em;
margin-left: 0.6em;
user-select: none;
}
.transcript .entry .tier-meta::before {
content: "; "; /* render as a lisp comment */
color: var(--muted);
opacity: 0.6;
}
.transcript .entry.error .tier-result { color: var(--err); }
/* Error line appended after partial streamed output — keeps the
* lines that already showed and tints just the trailing error. */
.transcript .tier-result .err-line { color: var(--err); }
.transcript .entry + .entry { margin-top: 0.4rem; }
/* ─── Prompt bar ───────────────────────────────────────────────── */
.prompt-bar {
/* Flows directly under the last transcript entry — connected, no gap.
* Sticks to the viewport bottom once the page scrolls past it. */
position: sticky;
bottom: 0;
z-index: 50;
display: grid;
grid-template-columns: auto 1fr auto auto;
gap: 0.4rem;
/* Baseline-align: sigil glyph sits on the textarea's first-line
* baseline whether the textarea is 1 row or 40. Center alignment
* with top-aligned sigil drifts visibly off the input baseline on
* single-line input; top alignment without baseline drifts the
* sigil ABOVE the input. Baseline tracks the first line in either
* case, which is what the eye expects from a prompt. */
align-items: baseline;
padding: 0.3rem 0.4rem 0.5rem;
background: var(--code-bg);
}
.prompt-bar .prompt-sigil {
color: var(--green);
font-weight: 600;
/* Match textarea font metrics so the baselines actually line up;
* a larger sigil font would push its baseline higher than the
* textarea's by the cap-height difference. */
font-family: var(--mono);
font-size: 0.92em;
line-height: 1.4;
}
.prompt-bar textarea {
resize: none;
background: var(--code-bg);
border: none;
color: var(--fg);
padding: 0.25rem 0;
font-family: var(--mono);
font-size: 0.92em;
/* Pin line-height so multi-line content + sigil baseline alignment
* stay predictable. Browser default for textarea is 'normal' which
* varies per font; matching .prompt-sigil keeps both baselines on
* the same grid line. */
line-height: 1.4;
min-height: 1.5em;
max-height: 30vh;
min-width: 0;
}
.prompt-bar textarea:focus { outline: none; border-color: var(--green); }
.prompt-bar select {
background: var(--code-bg);
border: 1px solid var(--rule);
color: var(--fg);
padding: 0.35rem 0.5rem;
font-family: var(--mono);
font-size: 0.85em;
border-radius: 3px;
}
.prompt-bar button.primary {
background: var(--green);
color: var(--bg);
border: 1px solid var(--green);
padding: 0.4rem 1rem;
font-family: var(--mono);
font-weight: 600;
border-radius: 3px;
cursor: pointer;
}
.prompt-bar button.primary:hover { filter: brightness(1.1); }
.prompt-bar button.primary:disabled { opacity: 0.4; cursor: wait; }
kbd {
font-family: var(--mono);
background: var(--code-bg);
padding: 0.05em 0.3em;
border: 1px solid var(--rule);
border-radius: 2px;
font-size: 0.85em;
}