Skip to content

Commit 064e0c7

Browse files
committed
fix: Run All output rendering — show results for every block
- Fixed: Run All executed blocks but never rendered output in DOM - Added renderBlockOutput() for bash/JS/SQL/math output display - Fixed findBlockContainer wrong CSS selectors for bash/JS containers - SQL default template now uses CREATE TABLE IF NOT EXISTS - Math default template pre-assigns x = 5 before expression - Updated test assertions to match new defaults
1 parent 31c4b64 commit 064e0c7

5 files changed

Lines changed: 336 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ TextAgent has undergone significant evolution since its inception. What started
459459

460460
| Date | Commits | Feature / Update |
461461
|------|---------|-----------------|
462+
| **2026-03-13** | | 🐛 **Run All output fix**`▶ Run All` now renders output for every block (bash, JS, SQL, math); fixed `findBlockContainer` producing wrong CSS selectors for bash/JS containers; added `renderBlockOutput()` for DOM rendering during Run All; SQL default template changed to `CREATE TABLE IF NOT EXISTS` for idempotent re-runs; math default template now pre-assigns `x = 5` before `x^2 + 2*x + 1` |
462463
| **2026-03-13** | | 📄 **Feature Showcase update** — synced in-app Feature Showcase template with all recent features; added 7 new rows to Features at a Glance table (Media Embedding, TTS, Game Builder, Finance Dashboard, Disk Workspace, Email to Self, Context Memory); 9 new dedicated sections with examples and tips; AI Document Tags table expanded to 9 tag types; AI model table expanded with Kokoro/Voxtral/Docling/Florence-2; Voice Dictation updated to dual-engine; 14 new task list items; Dev Tooling test count updated to 484 |
463464
| **2026-03-13** | `2e0e2ec` | 🎮 **Game Builder** — new `{{@Game:}}` tag for AI-generated and pre-built interactive games in markdown; engine selector pills (Canvas 2D / Three.js / P5.js); `@prebuilt:` field for 6 instant games (chess, snake, shooter, pong, breakout, maths quiz for kids); 📋 Import button to paste or upload external HTML game code with source viewer/editor; 📥 Export as standalone HTML; ⛶ fullscreen mode; `game-prebuilts.js` pre-built HTML library; `game-docgen.js` standalone module; `game-docgen.css` with purple gaming aesthetic and dark mode; single-line field parsing with lookahead regex; "Games for Kids" template category with all 6 pre-built games and syntax reference; `srcdoc` added to DOMPurify whitelist; 🎮 Game toolbar button in AI Tags dropdown |
464465
| **2026-03-12** | `9106fd1` | 🏷️ **@model Tag Field** — new `@model:` metadata field on all AI DocGen tag types (`{{@AI:}}`, `{{@Agent:}}`, `{{@Image:}}`, `{{@OCR:}}`, `{{@TTS:}}`, `{{@STT:}}`, `{{@Translate:}}`); persists selected model in document text for portability; intelligent defaults per tag type (OCR→`granite-docling`, TTS→`kokoro-tts`, STT→`voxtral-stt`, Image→`imagen-ultra`, AI/Agent→current model); dropdown shows all registered models, changing it syncs `@model:` back to editor; validated against `AI_MODELS` registry (invalid IDs silently ignored); fully backward-compatible with existing tags |
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Run All Output Fix — Bug Fixes for Code Block Execution
2+
3+
- Fixed: "Run All" executed blocks but did not show output for any block (bash, JS, SQL, math)
4+
- Fixed: `findBlockContainer` produced wrong CSS selectors for bash (`executable-bash-container`) and JavaScript (`executable-javascript-container`) — now uses `mapLangToContainer()`
5+
- Added `renderBlockOutput()` function in exec-controller.js to render results into `.code-output` DOM elements during Run All
6+
- Output rendering handles SQL (formatted HTML table), math (expression → result lines), and all others (plain text)
7+
- Fixed: Default SQL template used `CREATE TABLE` which errored on re-run — changed to `CREATE TABLE IF NOT EXISTS`
8+
- Fixed: Default math template `x^2 + 2*x + 1` caused "Undefined symbol x" — added `x = 5` assignment line
9+
- Updated exec-hello-world test assertions to match new SQL and math defaults
10+
11+
---
12+
13+
## Summary
14+
Fixed multiple bugs in the code execution system: "Run All" now renders output for every block, the SQL default template is idempotent, and the math default template defines variables before using them.
15+
16+
---
17+
18+
## 1. Run All Output Rendering
19+
**Files:** `js/exec-controller.js`
20+
**What:** Added `renderBlockOutput()` function (~80 lines) that creates `.code-output` DOM elements and renders results after each block executes in `runAll`. Handles three output formats: SQL (HTML table from pipe-delimited adapter output), math (expression → result formatting), and plain text (bash, python, JS). Also fixed `findBlockContainer` which used broken inline class-name logic — replaced with a call to the existing `mapLangToContainer()` function.
21+
**Impact:** "Run All" now shows output for every block type, matching the behavior of individual Run buttons.
22+
23+
## 2. SQL Template Idempotency
24+
**Files:** `js/coding-blocks.js`
25+
**What:** Changed default SQL template from `CREATE TABLE greetings` to `CREATE TABLE IF NOT EXISTS greetings`.
26+
**Impact:** SQL blocks no longer error with "table already exists" when re-run or when using Run All.
27+
28+
## 3. Math Template Fix
29+
**Files:** `js/coding-blocks.js`
30+
**What:** Added `x = 5` assignment line before `x^2 + 2*x + 1` in the default math template.
31+
**Impact:** Math evaluation no longer errors with "Undefined symbol x".
32+
33+
## 4. Test Updates
34+
**Files:** `tests/feature/exec-hello-world.spec.js`
35+
**What:** Updated SQL assertions to use `CREATE TABLE IF NOT EXISTS` to match the new default template.
36+
**Impact:** Tests pass with updated defaults.
37+
38+
---
39+
40+
## Files Changed (3 total)
41+
42+
| File | Lines Changed | Type |
43+
|------|:---:|------|
44+
| `js/exec-controller.js` | +85 −1 | Run All output rendering + findBlockContainer fix |
45+
| `js/coding-blocks.js` | +2 −2 | SQL IF NOT EXISTS + math variable assignment |
46+
| `tests/feature/exec-hello-world.spec.js` | +2 −2 | Updated test assertions |

js/coding-blocks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
// --- Coding block templates ---
99
var CODING_TEMPLATES = {
1010
'coding-bash': '```bash\n# Your bash commands\necho "Hello, World!"\n```\n',
11-
'coding-math': '```math\nx^2 + 2*x + 1\n```\n',
11+
'coding-math': '```math\nx = 5\nx^2 + 2*x + 1\n```\n',
1212
'coding-python': '```python\n# Your Python code\nprint("Hello, World!")\n```\n',
1313
'coding-html': '```html\n<!-- Your HTML -->\n<h3>Hello, World!</h3>\n<p>Edit this HTML and click <strong>Preview</strong> to see it rendered.</p>\n```\n',
1414
'coding-js': '```javascript\n// Your JavaScript\nconsole.log("Hello, World!");\n```\n',
15-
'coding-sql': '```sql\nCREATE TABLE greetings (id INTEGER PRIMARY KEY, message TEXT);\nINSERT INTO greetings VALUES (1, \'Hello, World!\');\nSELECT * FROM greetings;\n```\n'
15+
'coding-sql': '```sql\nCREATE TABLE IF NOT EXISTS greetings (id INTEGER PRIMARY KEY, message TEXT);\nINSERT INTO greetings VALUES (1, \'Hello, World!\');\nSELECT * FROM greetings;\n```\n'
1616
};
1717

1818
Object.keys(CODING_TEMPLATES).forEach(function (action) {

js/exec-controller.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
if (!preview) return null;
8282

8383
if (block.type === 'code') {
84-
var containerClass = 'executable-' + (block.lang === 'sh' || block.lang === 'shell' ? 'code' : block.lang.replace('html-autorun', 'html')) + '-container';
84+
var containerClass = mapLangToContainer(block.lang);
8585
var containers = preview.querySelectorAll('.' + containerClass);
8686

8787
// Find by matching order — count how many code blocks of this type
@@ -162,6 +162,87 @@
162162
}
163163
}
164164

165+
// ========================================
166+
// Render block output into the DOM
167+
// ========================================
168+
169+
function renderBlockOutput(block, result, error) {
170+
var container = findBlockContainer(block);
171+
if (!container) return;
172+
173+
var escapeHtml = M._exec && M._exec.escapeHtml ? M._exec.escapeHtml : function (s) {
174+
var d = document.createElement('div'); d.textContent = s; return d.innerHTML;
175+
};
176+
177+
var outputEl = container.querySelector('.code-output');
178+
if (!outputEl) {
179+
outputEl = document.createElement('div');
180+
outputEl.className = 'code-output';
181+
container.appendChild(outputEl);
182+
}
183+
outputEl.style.display = 'block';
184+
185+
if (error) {
186+
outputEl.innerHTML = '<span class="code-output-error">✖ ' + escapeHtml(error.message || String(error)) + '</span>';
187+
return;
188+
}
189+
190+
if (result === null || result === undefined) {
191+
outputEl.innerHTML = '<span class="code-output-muted">(no output)</span>';
192+
return;
193+
}
194+
195+
var resultStr = typeof result === 'string' ? result : JSON.stringify(result);
196+
197+
// For SQL blocks with tabular output, render as a formatted table
198+
if (block.runtimeKey === 'sql' && resultStr.indexOf(' | ') !== -1) {
199+
var sqlLines = resultStr.split('\n');
200+
var html = '<div class="sql-result-table-wrap"><table class="sql-result-table">';
201+
for (var li = 0; li < sqlLines.length; li++) {
202+
var line = sqlLines[li];
203+
if (/^-+(\s*\|\s*-+)*$/.test(line)) continue; // skip separator rows
204+
if (/^\d+ row\(s\)$/.test(line)) {
205+
html += '</table><div class="sql-row-count">' + escapeHtml(line) + '</div></div>';
206+
continue;
207+
}
208+
var cells = line.split(' | ');
209+
var tag = li === 0 ? 'th' : 'td';
210+
html += '<tr>';
211+
for (var ci = 0; ci < cells.length; ci++) {
212+
html += '<' + tag + '>' + escapeHtml(cells[ci]) + '</' + tag + '>';
213+
}
214+
html += '</tr>';
215+
}
216+
html += '</table></div>';
217+
outputEl.innerHTML = html;
218+
return;
219+
}
220+
221+
// For math blocks, render with expression → result formatting
222+
if (block.runtimeKey === 'math' && resultStr.indexOf('→') !== -1) {
223+
var mathLines = resultStr.split('\n');
224+
var mathHtml = '';
225+
for (var mi = 0; mi < mathLines.length; mi++) {
226+
var parts = mathLines[mi].split(' → ');
227+
if (parts.length === 2) {
228+
var isErr = parts[1].indexOf('❌') === 0;
229+
if (isErr) {
230+
mathHtml += '<div class="math-result-line"><span class="math-result-expr">' + escapeHtml(parts[0]) + '</span> <span class="code-output-error">→ ' + escapeHtml(parts[1]) + '</span></div>';
231+
} else {
232+
mathHtml += '<div class="math-result-line"><span class="math-result-expr">' + escapeHtml(parts[0]) + '</span> <span class="math-result-arrow">→</span> <span class="math-result-value">' + escapeHtml(parts[1]) + '</span></div>';
233+
}
234+
} else {
235+
mathHtml += '<div>' + escapeHtml(mathLines[mi]) + '</div>';
236+
}
237+
}
238+
outputEl.innerHTML = mathHtml || '<span class="code-output-muted">(no result)</span>';
239+
return;
240+
}
241+
242+
// Default: render as plain text (bash, python, js, etc.)
243+
outputEl.innerHTML = '<span class="code-output-stdout">' + escapeHtml(resultStr) + '</span>';
244+
}
245+
165246
// ========================================
166247
// Run All
167248
// ========================================
@@ -233,12 +314,14 @@
233314
var result = await executeBlock(block);
234315
block.result = result;
235316
updateBlockStatus(block, 'done');
317+
renderBlockOutput(block, result, null);
236318
_currentRun.completed++;
237319
emit('exec:block-done', { block: block, result: result, index: i });
238320
} catch (err) {
239321
block.result = null;
240322
block.error = err;
241323
updateBlockStatus(block, 'error');
324+
renderBlockOutput(block, null, err);
242325
_currentRun.errors++;
243326
emit('exec:block-error', { block: block, error: err, index: i });
244327
console.error('[ExecController] Block error:', block.id, err);
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// @ts-check
2+
import { test, expect } from '@playwright/test';
3+
4+
/**
5+
* Hello-World smoke tests for every executable code-block language.
6+
* Each test types a minimal "Hello, World!" snippet, clicks the per-block
7+
* Run button, and asserts that the output area shows the expected text.
8+
*
9+
* These act as a permanent canary — if any runtime breaks, this suite
10+
* will catch it immediately.
11+
*/
12+
13+
const EDITOR_SELECTOR = '#markdown-editor';
14+
15+
/** Helper: hover over a container to reveal the toolbar, then click Run.
16+
* Each language uses a different class: code-run-btn, python-run-btn,
17+
* js-run-btn, sql-run-btn — so we match any of them.
18+
*/
19+
async function clickRunButton(container) {
20+
// Hover to reveal the toolbar (it may be hidden by default)
21+
await container.hover();
22+
const runBtn = container.locator('.code-run-btn, .python-run-btn, .js-run-btn, .sql-run-btn');
23+
await runBtn.waitFor({ state: 'visible', timeout: 10_000 });
24+
await runBtn.click();
25+
}
26+
27+
test.describe('Hello World — Code Block Execution', () => {
28+
// Give runtimes (Pyodide, sql.js, just-bash) plenty of time to load
29+
test.setTimeout(120_000);
30+
31+
test.beforeEach(async ({ page }) => {
32+
await page.goto('/');
33+
await page.waitForSelector(EDITOR_SELECTOR, { state: 'visible' });
34+
await page.waitForFunction(
35+
() => window.MDView && window.MDView.currentViewMode === 'split',
36+
);
37+
// Wait for all exec-engine modules + runtimes to initialise
38+
await page.waitForTimeout(5000);
39+
});
40+
41+
// ─── Bash ────────────────────────────────────────────────
42+
test('bash: echo Hello, World!', async ({ page }) => {
43+
const md = [
44+
'```bash',
45+
'# Your bash commands',
46+
'echo "Hello, World!"',
47+
'```',
48+
].join('\n');
49+
50+
await page.locator(EDITOR_SELECTOR).fill(md);
51+
await page.waitForTimeout(1500);
52+
53+
const container = page.locator('.executable-code-container').first();
54+
await expect(container).toBeVisible();
55+
56+
// Click the per-block Run button
57+
await clickRunButton(container);
58+
59+
// Wait for .code-output to appear with content
60+
const output = container.locator('.code-output');
61+
await expect(output).toBeVisible({ timeout: 30_000 });
62+
await expect(output).toContainText('Hello, World!', { timeout: 15_000 });
63+
64+
// No errors
65+
const errorSpan = container.locator('.code-output-error');
66+
await expect(errorSpan).toHaveCount(0);
67+
});
68+
69+
// ─── Python ──────────────────────────────────────────────
70+
test('python: print Hello, World!', async ({ page }) => {
71+
const md = [
72+
'```python',
73+
'# Your Python code',
74+
'print("Hello, World!")',
75+
'```',
76+
].join('\n');
77+
78+
await page.locator(EDITOR_SELECTOR).fill(md);
79+
await page.waitForTimeout(1500);
80+
81+
const container = page.locator('.executable-python-container').first();
82+
await expect(container).toBeVisible();
83+
84+
await clickRunButton(container);
85+
86+
const output = container.locator('.code-output');
87+
await expect(output).toBeVisible({ timeout: 60_000 });
88+
await expect(output).toContainText('Hello, World!', { timeout: 30_000 });
89+
90+
const errorSpan = container.locator('.code-output-error');
91+
await expect(errorSpan).toHaveCount(0);
92+
});
93+
94+
// ─── JavaScript ──────────────────────────────────────────
95+
test('javascript: console.log Hello, World!', async ({ page }) => {
96+
const md = [
97+
'```javascript',
98+
'// Your JavaScript',
99+
'console.log("Hello, World!");',
100+
'```',
101+
].join('\n');
102+
103+
await page.locator(EDITOR_SELECTOR).fill(md);
104+
await page.waitForTimeout(1500);
105+
106+
const container = page.locator('.executable-js-container').first();
107+
await expect(container).toBeVisible();
108+
109+
await clickRunButton(container);
110+
111+
const output = container.locator('.code-output');
112+
await expect(output).toBeVisible({ timeout: 15_000 });
113+
await expect(output).toContainText('Hello, World!', { timeout: 10_000 });
114+
115+
const errorSpan = container.locator('.code-output-error');
116+
await expect(errorSpan).toHaveCount(0);
117+
});
118+
119+
// ─── SQL ─────────────────────────────────────────────────
120+
test('sql: CREATE, INSERT, SELECT', async ({ page }) => {
121+
const md = [
122+
'```sql',
123+
'CREATE TABLE IF NOT EXISTS greetings (id INTEGER PRIMARY KEY, message TEXT);',
124+
"INSERT INTO greetings VALUES (1, 'Hello, World!');",
125+
'SELECT * FROM greetings;',
126+
'```',
127+
].join('\n');
128+
129+
await page.locator(EDITOR_SELECTOR).fill(md);
130+
await page.waitForTimeout(1500);
131+
132+
const container = page.locator('.executable-sql-container').first();
133+
await expect(container).toBeVisible();
134+
135+
await clickRunButton(container);
136+
137+
const output = container.locator('.code-output');
138+
await expect(output).toBeVisible({ timeout: 30_000 });
139+
// SQL output renders as a table; assert the data row appears
140+
await expect(output).toContainText('Hello, World!', { timeout: 15_000 });
141+
142+
const errorSpan = container.locator('.code-output-error');
143+
await expect(errorSpan).toHaveCount(0);
144+
});
145+
146+
// ─── Combined: all four in one document ──────────────────
147+
test('all four languages in a single document', async ({ page }) => {
148+
const md = [
149+
'```bash',
150+
'echo "Hello, World!"',
151+
'```',
152+
'',
153+
'```python',
154+
'print("Hello, World!")',
155+
'```',
156+
'',
157+
'```javascript',
158+
'console.log("Hello, World!");',
159+
'```',
160+
'',
161+
'```sql',
162+
'CREATE TABLE IF NOT EXISTS greetings (id INTEGER PRIMARY KEY, message TEXT);',
163+
"INSERT INTO greetings VALUES (1, 'Hello, World!');",
164+
'SELECT * FROM greetings;',
165+
'```',
166+
].join('\n');
167+
168+
await page.locator(EDITOR_SELECTOR).fill(md);
169+
await page.waitForTimeout(2000);
170+
171+
// All four containers should render
172+
const bashContainer = page.locator('.executable-code-container').first();
173+
const pythonContainer = page.locator('.executable-python-container').first();
174+
const jsContainer = page.locator('.executable-js-container').first();
175+
const sqlContainer = page.locator('.executable-sql-container').first();
176+
177+
await expect(bashContainer).toBeVisible();
178+
await expect(pythonContainer).toBeVisible();
179+
await expect(jsContainer).toBeVisible();
180+
await expect(sqlContainer).toBeVisible();
181+
182+
// Run each block individually in document order
183+
await clickRunButton(bashContainer);
184+
const bashOutput = bashContainer.locator('.code-output');
185+
await expect(bashOutput).toContainText('Hello, World!', { timeout: 30_000 });
186+
187+
await clickRunButton(pythonContainer);
188+
const pythonOutput = pythonContainer.locator('.code-output');
189+
await expect(pythonOutput).toContainText('Hello, World!', { timeout: 60_000 });
190+
191+
await clickRunButton(jsContainer);
192+
const jsOutput = jsContainer.locator('.code-output');
193+
await expect(jsOutput).toContainText('Hello, World!', { timeout: 15_000 });
194+
195+
await clickRunButton(sqlContainer);
196+
const sqlOutput = sqlContainer.locator('.code-output');
197+
await expect(sqlOutput).toContainText('Hello, World!', { timeout: 30_000 });
198+
199+
// Zero errors across all containers
200+
const errors = page.locator('.code-output-error');
201+
await expect(errors).toHaveCount(0);
202+
});
203+
});

0 commit comments

Comments
 (0)