repl: terminal-style transcript — drop card chrome

Reads as one continuous stream now. Each entry is just:
  λ> <input>
  <output>   ; tier · NNms

No left border, no boxed cards, no side-column tier label. Output
indents under the prompt (3ch) using monospace ch units. Tier+time
render as a Lisp-comment-style suffix in muted color.

Prompt bar: borderless textarea on the code-bg surface so the input
visually joins the transcript above. Placeholder cut to "(+ 1 2)" —
the surrounding text already explains the semantics.

Multi-line inputs keep prompt continuation marks ("..").
This commit is contained in:
russell@unturf.com 2026-06-14 13:17:17 -04:00
parent b3bcff5f08
commit eb58cdc33c
No known key found for this signature in database
6 changed files with 132 additions and 106 deletions

View file

@ -101,7 +101,7 @@
<section class="prompt-bar" id="prompt-bar" hidden>
<span class="prompt-sigil">λ&gt;</span>
<textarea id="input" rows="1" spellcheck="false"
placeholder="(+ 1 2) — enter to send, shift+enter for newline"></textarea>
placeholder="(+ 1 2)"></textarea>
<select id="tier-select" title="which tier evaluates this input">
<option value="python">python (pyodide)</option>
<option value="c" selected>c (emcc)</option>

View file

@ -168,81 +168,85 @@ body.repl {
/* ─── Transcript ───────────────────────────────────────────────── */
/* Transcript reads as one continuous terminal stream no card chrome.
* Prompt + output stack with minimal vertical space, tier+time annotate
* as a subtle comment-style suffix. */
.transcript-wrap {
overflow: auto;
padding: 1rem;
padding: 0.5rem 1rem 0;
background: var(--code-bg);
}
.transcript {
display: grid;
gap: 0.6rem;
font-family: var(--mono);
font-size: 0.9em;
line-height: 1.5;
font-size: 0.92em;
line-height: 1.45;
max-width: 90rem;
margin: 0 auto;
padding-bottom: 0.4rem;
}
.transcript .entry {
display: grid;
gap: 0.25rem;
padding: 0.4rem 0.6rem;
border-left: 2px solid var(--rule);
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: grid;
grid-template-columns: 7rem 1fr;
gap: 0.6rem;
padding: 0.25rem 0;
border-top: 1px dashed var(--rule);
}
.transcript .entry .tier-output:first-of-type { border-top: none; }
.transcript .entry .tier-label {
color: var(--muted);
font-size: 0.7em;
text-transform: uppercase;
letter-spacing: 0.08em;
padding-top: 0.15rem;
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-time {
.transcript .entry .tier-meta {
color: var(--muted);
font-size: 0.8em;
margin-left: 0.4rem;
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); }
.transcript .entry + .entry { margin-top: 0.4rem; }
/* ─── Prompt bar ───────────────────────────────────────────────── */
.prompt-bar {
display: grid;
grid-template-columns: auto 1fr auto auto;
gap: 0.5rem;
align-items: end;
padding: 0.5rem 1rem 0.8rem;
gap: 0.4rem;
align-items: center;
padding: 0.3rem 1rem;
border-top: 1px solid var(--rule);
background: var(--bg);
background: var(--code-bg);
}
.prompt-bar .prompt-sigil {
color: var(--green);
font-weight: 600;
padding: 0.4rem 0 0;
font-size: 0.95em;
}
.prompt-bar textarea {
resize: none;
background: var(--code-bg);
border: 1px solid var(--rule);
border: none;
color: var(--fg);
padding: 0.45rem 0.7rem;
padding: 0.25rem 0;
font-family: var(--mono);
font-size: 0.9em;
border-radius: 3px;
min-height: 1.6em;
font-size: 0.92em;
min-height: 1.5em;
max-height: 30vh;
min-width: 0;
}

View file

@ -253,7 +253,8 @@ function renderAll() {
tabsEl.appendChild(el);
}
// Transcript
// Transcript — terminal-style: prompt line, then output lines underneath
// indented under the prompt with a tier-tag suffix as a Lisp comment.
transcriptEl.innerHTML = "";
const tab = activeTab();
if (!tab) return;
@ -261,25 +262,33 @@ function renderAll() {
for (const entry of tab.transcript) {
const block = document.createElement("div");
block.className = "entry" + (entry.kind === "error" ? " error" : "");
const prompt = document.createElement("div");
prompt.className = "prompt-line";
prompt.textContent = "λ> " + entry.input;
block.appendChild(prompt);
// Render the input across one or more lines (preserve newlines).
const inputLines = entry.input.split(/\n/);
for (let i = 0; i < inputLines.length; i++) {
const line = document.createElement("div");
line.className = "prompt-line";
const sigil = document.createElement("span");
sigil.className = "sigil";
sigil.textContent = i === 0 ? "λ>" : "..";
line.appendChild(sigil);
line.appendChild(document.createTextNode(inputLines[i]));
block.appendChild(line);
}
for (const r of (entry.results || [])) {
const row = document.createElement("div");
row.className = "tier-output";
const lab = document.createElement("div");
lab.className = "tier-label";
lab.textContent = TIER_LABEL[r.tier] || r.tier;
const tm = document.createElement("span");
tm.className = "tier-time";
tm.textContent = ` ${(r.elapsed | 0)}ms`;
lab.appendChild(tm);
const out = document.createElement("div");
out.className = "tier-result";
out.textContent = r.output || (r.error ? "error: " + r.error : "");
row.appendChild(lab);
row.appendChild(out);
const result = document.createElement("span");
result.className = "tier-result";
const text = (r.output != null ? r.output : "");
const errText = r.error ? "error: " + r.error : "";
result.textContent = (text + (errText && text ? "\n" : "") + errText).replace(/\n$/, "");
const meta = document.createElement("span");
meta.className = "tier-meta";
meta.textContent = `${TIER_LABEL[r.tier] || r.tier} · ${(r.elapsed | 0)}ms`;
row.appendChild(result);
row.appendChild(meta);
block.appendChild(row);
}
transcriptEl.appendChild(block);

View file

@ -101,7 +101,7 @@
<section class="prompt-bar" id="prompt-bar" hidden>
<span class="prompt-sigil">λ&gt;</span>
<textarea id="input" rows="1" spellcheck="false"
placeholder="(+ 1 2) — enter to send, shift+enter for newline"></textarea>
placeholder="(+ 1 2)"></textarea>
<select id="tier-select" title="which tier evaluates this input">
<option value="python">python (pyodide)</option>
<option value="c" selected>c (emcc)</option>

View file

@ -168,81 +168,85 @@ body.repl {
/* ─── Transcript ───────────────────────────────────────────────── */
/* Transcript reads as one continuous terminal stream no card chrome.
* Prompt + output stack with minimal vertical space, tier+time annotate
* as a subtle comment-style suffix. */
.transcript-wrap {
overflow: auto;
padding: 1rem;
padding: 0.5rem 1rem 0;
background: var(--code-bg);
}
.transcript {
display: grid;
gap: 0.6rem;
font-family: var(--mono);
font-size: 0.9em;
line-height: 1.5;
font-size: 0.92em;
line-height: 1.45;
max-width: 90rem;
margin: 0 auto;
padding-bottom: 0.4rem;
}
.transcript .entry {
display: grid;
gap: 0.25rem;
padding: 0.4rem 0.6rem;
border-left: 2px solid var(--rule);
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: grid;
grid-template-columns: 7rem 1fr;
gap: 0.6rem;
padding: 0.25rem 0;
border-top: 1px dashed var(--rule);
}
.transcript .entry .tier-output:first-of-type { border-top: none; }
.transcript .entry .tier-label {
color: var(--muted);
font-size: 0.7em;
text-transform: uppercase;
letter-spacing: 0.08em;
padding-top: 0.15rem;
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-time {
.transcript .entry .tier-meta {
color: var(--muted);
font-size: 0.8em;
margin-left: 0.4rem;
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); }
.transcript .entry + .entry { margin-top: 0.4rem; }
/* ─── Prompt bar ───────────────────────────────────────────────── */
.prompt-bar {
display: grid;
grid-template-columns: auto 1fr auto auto;
gap: 0.5rem;
align-items: end;
padding: 0.5rem 1rem 0.8rem;
gap: 0.4rem;
align-items: center;
padding: 0.3rem 1rem;
border-top: 1px solid var(--rule);
background: var(--bg);
background: var(--code-bg);
}
.prompt-bar .prompt-sigil {
color: var(--green);
font-weight: 600;
padding: 0.4rem 0 0;
font-size: 0.95em;
}
.prompt-bar textarea {
resize: none;
background: var(--code-bg);
border: 1px solid var(--rule);
border: none;
color: var(--fg);
padding: 0.45rem 0.7rem;
padding: 0.25rem 0;
font-family: var(--mono);
font-size: 0.9em;
border-radius: 3px;
min-height: 1.6em;
font-size: 0.92em;
min-height: 1.5em;
max-height: 30vh;
min-width: 0;
}

View file

@ -253,7 +253,8 @@ function renderAll() {
tabsEl.appendChild(el);
}
// Transcript
// Transcript — terminal-style: prompt line, then output lines underneath
// indented under the prompt with a tier-tag suffix as a Lisp comment.
transcriptEl.innerHTML = "";
const tab = activeTab();
if (!tab) return;
@ -261,25 +262,33 @@ function renderAll() {
for (const entry of tab.transcript) {
const block = document.createElement("div");
block.className = "entry" + (entry.kind === "error" ? " error" : "");
const prompt = document.createElement("div");
prompt.className = "prompt-line";
prompt.textContent = "λ> " + entry.input;
block.appendChild(prompt);
// Render the input across one or more lines (preserve newlines).
const inputLines = entry.input.split(/\n/);
for (let i = 0; i < inputLines.length; i++) {
const line = document.createElement("div");
line.className = "prompt-line";
const sigil = document.createElement("span");
sigil.className = "sigil";
sigil.textContent = i === 0 ? "λ>" : "..";
line.appendChild(sigil);
line.appendChild(document.createTextNode(inputLines[i]));
block.appendChild(line);
}
for (const r of (entry.results || [])) {
const row = document.createElement("div");
row.className = "tier-output";
const lab = document.createElement("div");
lab.className = "tier-label";
lab.textContent = TIER_LABEL[r.tier] || r.tier;
const tm = document.createElement("span");
tm.className = "tier-time";
tm.textContent = ` ${(r.elapsed | 0)}ms`;
lab.appendChild(tm);
const out = document.createElement("div");
out.className = "tier-result";
out.textContent = r.output || (r.error ? "error: " + r.error : "");
row.appendChild(lab);
row.appendChild(out);
const result = document.createElement("span");
result.className = "tier-result";
const text = (r.output != null ? r.output : "");
const errText = r.error ? "error: " + r.error : "";
result.textContent = (text + (errText && text ? "\n" : "") + errText).replace(/\n$/, "");
const meta = document.createElement("span");
meta.className = "tier-meta";
meta.textContent = `${TIER_LABEL[r.tier] || r.tier} · ${(r.elapsed | 0)}ms`;
row.appendChild(result);
row.appendChild(meta);
block.appendChild(row);
}
transcriptEl.appendChild(block);