Add a Run instantly option that folds the steps away

The 100ms pause between steps is what makes the log readable while it fills, but it also means the fibonacci example takes 210 of them, three and a half minutes of watching, to answer 8. Run instantly skips the pause. The checkbox is read per step, so ticking it part way through a long run takes effect immediately. A run with nothing to watch does not want its trace spelled out either, so afterwards everything between the program and what it ended on goes into a details element labelled with the number of steps. The whole run happens in one task, so the rows never paint before being folded. The first and last rows stay out of the fold: it keeps the log reading as program, what happened, result - and resetLog builds each history entry's summary from log.firstChild.lastChild, so a fold in first position would collapse every past run under a summary holding its entire trace. Folding happens in a finally, so a program that throws keeps its steps tidy too, with the error left visible below them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

author
Marijn Besseling <njirambem@gmail.com> · 2026-09-12 19:37 UTC
commit
a273ce43a6e60b176db02c755322738a916f179d
parent
7afeca15f1
tree
browse at this commit

2 files changed +23 -1

Blog/Components/Pages/Concat.razor +4 -0

@@ -6,6 +6,10 @@
6 6 <form id="form">
7 7 <textarea id="input" placeholder="1 2 +" rows="5"></textarea>
8 8 <input type="submit" value="Run program">
9 + <label for="instant">
10 + Run instantly
11 + <input type="checkbox" id="instant">
12 + </label>
9 13 </form>
10 14 <Log/>
11 15

Blog/Components/Pages/Concat.razor.js +19 -1

@@ -5,6 +5,7 @@ let definitions = {};
5 5
6 6 const form = getById("form");
7 7 const input = getById("input");
8 +const instant = getById("instant");
8 9 const log = getById("log");
9 10
10 11 form.onsubmit = async (event) => {
@@ -40,9 +41,26 @@ async function submitForm() {
40 41 } catch (error) {
41 42 writeError(error);
42 43 console.log(error);
44 + } finally {
45 + if (instant.checked) foldSteps();
43 46 }
44 47 }
45 48
49 +/**
50 + * A run with no pauses between the steps arrives all at once, so everything
51 + * between the program and what it ended on is folded away.
52 + */
53 +function foldSteps() {
54 + let rows = [...log.children];
55 + if (rows.length <= 2) return;
56 +
57 + let steps = rows.slice(1, -1);
58 + let label = steps.length === 1 ? "1 step" : `${steps.length} steps`;
59 + let fold = h("details", {class: "steps"}, [h("summary", label), ...steps]);
60 +
61 + log.insertBefore(fold, rows.at(-1));
62 +}
63 +
46 64 function splitWords(input) {
47 65 return input
48 66 .trim()
@@ -92,7 +110,7 @@ async function evalWords(inputWords) {
92 110 let [word, ...rest] = words;
93 111 [stack, words] = evalWord(word, stack, rest);
94 112
95 - await new Promise((r) => setTimeout(r, 100));
113 + if (!instant.checked) await new Promise((r) => setTimeout(r, 100));
96 114 }
97 115 await writeLog(stack, words);
98 116 return stack;