You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The browser runtime currently treats IO.print as text-to-DOM (writes go to a <pre> element appended to the page) without interpreting any ANSI escape sequences. Terminal-style Vera programs that use ANSI escapes for cursor positioning, screen clearing, or coloring — completely standard for animated terminal output — render their escape codes as literal text in the DOM, defeating the animation.
Bounded-scope fix: have the browser runtime interpret a small ANSI subset (cursor positioning, screen clearing, line erase, basic colors) and translate them into DOM mutations on a target <pre> element. Estimated ~200 lines of JS. Pairs with the JSPI-driven IO.sleep fix so terminal Vera programs run unchanged on both targets.
Discovery context
An agent writing a browser variant of Conway's Life on current main reported this as the second of two related obstacles to "write once, run anywhere":
"ANSI escapes don't render because the browser runtime treats IO.print as text-to-DOM rather than text-to-terminal."
Their working terminal program (life.vera) used \u{1B}[2J (clear screen), \u{1B}[H (cursor home), \u{1B}[K (erase to end of line) on each frame to overwrite the previous render in place. In the browser these escape sequences appeared as literal control characters in the page output, so each frame appended below the previous instead of overwriting — turning what should be an animation into a long stripey text dump.
The agent's workaround was a separate browser-shaped program (life_web.vera) that emits packed 0/1 strings to stdout, paired with a custom JavaScript canvas driver — bypassing both IO.print rendering and ANSI escapes entirely. That works but isn't portable and isn't ergonomic.
Proposed scope
A minimal ANSI subset is sufficient for cursor-addressable terminal apps (the kind animations and TUIs use). The minimum useful set:
The runtime maintains a virtual screen buffer (rows × cols of cells with text + style) and applies these escapes against that buffer. On each IO.print, after parsing escapes, the buffer is rendered as styled <span>s into a target <pre id="vera-screen"> element.
xterm.js is the maximalist implementation (very large, full xterm fidelity). The proposed scope is much smaller — just enough for cursor-addressable Vera programs. ~200 lines of JS.
Why this is high-leverage
Closes the rendering half of the terminal-vs-browser IO seam without language changes. Combined with the JSPI-IO.sleep fix, a typical terminal Vera program (IO.sleep for pacing + ANSI for screen control) renders identically on both targets.
Compatibility with existing programs: terminal Vera programs that already use ANSI escapes (mostly examples in examples/ like animations, simple TUIs) run as-is in the browser without modification.
Acceptance
The runtime parses and acts on at least the cursor-positioning + screen/line-clearing escape subset listed above.
A program that does IO.print("\u{1B}[2J\u{1B}[H") followed by frame content renders correctly in the browser (frame N replaces frame N-1, no concatenated stripes).
A test in tests/test_browser.py verifies a small terminal animation (e.g., a 5-cell counter that increments via cursor-home) renders with the expected final DOM state after N frames.
#608 — the framing issue ("terminal-vs-browser IO seam")
The agent's full design memo is posted as a comment on the umbrella issue: #608 design memo. Key paragraph: "Cursor positioning ([ESC] [H, [K, [2J), basic colours. Render to a <pre> element, not the whole page. This is bounded scope — maybe 200 lines of JS — and lets terminal-style programs render in the browser with no JS driver at all."
Summary
The browser runtime currently treats
IO.printas text-to-DOM (writes go to a<pre>element appended to the page) without interpreting any ANSI escape sequences. Terminal-style Vera programs that use ANSI escapes for cursor positioning, screen clearing, or coloring — completely standard for animated terminal output — render their escape codes as literal text in the DOM, defeating the animation.Bounded-scope fix: have the browser runtime interpret a small ANSI subset (cursor positioning, screen clearing, line erase, basic colors) and translate them into DOM mutations on a target
<pre>element. Estimated ~200 lines of JS. Pairs with the JSPI-drivenIO.sleepfix so terminal Vera programs run unchanged on both targets.Discovery context
An agent writing a browser variant of Conway's Life on current main reported this as the second of two related obstacles to "write once, run anywhere":
Their working terminal program (
life.vera) used\u{1B}[2J(clear screen),\u{1B}[H(cursor home),\u{1B}[K(erase to end of line) on each frame to overwrite the previous render in place. In the browser these escape sequences appeared as literal control characters in the page output, so each frame appended below the previous instead of overwriting — turning what should be an animation into a long stripey text dump.The agent's workaround was a separate browser-shaped program (
life_web.vera) that emits packed0/1strings to stdout, paired with a custom JavaScript canvas driver — bypassing bothIO.printrendering and ANSI escapes entirely. That works but isn't portable and isn't ergonomic.Proposed scope
A minimal ANSI subset is sufficient for cursor-addressable terminal apps (the kind animations and TUIs use). The minimum useful set:
ESC[H(home),ESC[<row>;<col>H(move),ESC[<n>A/B/C/D(relative move).ESC[2J(clear screen),ESC[K(erase to EOL),ESC[J(erase below cursor).ESC[3<n>m/ESC[4<n>m(foreground / background 8-color),ESC[0m(reset).The runtime maintains a virtual screen buffer (rows × cols of cells with text + style) and applies these escapes against that buffer. On each
IO.print, after parsing escapes, the buffer is rendered as styled<span>s into a target<pre id="vera-screen">element.xterm.jsis the maximalist implementation (very large, full xterm fidelity). The proposed scope is much smaller — just enough for cursor-addressable Vera programs. ~200 lines of JS.Why this is high-leverage
IO.sleepfix, a typical terminal Vera program (IO.sleepfor pacing + ANSI for screen control) renders identically on both targets.examples/like animations, simple TUIs) run as-is in the browser without modification.Acceptance
IO.print("\u{1B}[2J\u{1B}[H")followed by frame content renders correctly in the browser (frame N replaces frame N-1, no concatenated stripes).tests/test_browser.pyverifies a small terminal animation (e.g., a 5-cell counter that increments via cursor-home) renders with the expected final DOM state after N frames.life.vera, terminal version) runs unchanged onvera compile --target browserand animates correctly.Related
IO.sleepissue — the timing half of the same seam; the two together close the gap<pre>element, not the whole page. This is bounded scope — maybe 200 lines of JS — and lets terminal-style programs render in the browser with no JS driver at all."