Skip to content

Browser runtime: interpret a minimal ANSI escape subset so terminal-style Vera programs render unchanged #610

@aallan

Description

@aallan

Summary

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:

  • Cursor positioning: ESC[H (home), ESC[<row>;<col>H (move), ESC[<n>A/B/C/D (relative move).
  • Screen / line clearing: ESC[2J (clear screen), ESC[K (erase to EOL), ESC[J (erase below cursor).
  • Basic colors (optional first step): 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.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.
  • Worked example: The Vera Conway's Life from PR SKILL doc fixes + ROADMAP browser-target tier from Conway's Life agent experiments #601's transcript would Just Work — same source, both targets, no JS driver.
  • 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.
  • The full Conway's Life program from PR SKILL doc fixes + ROADMAP browser-target tier from Conway's Life agent experiments #601 (life.vera, terminal version) runs unchanged on vera compile --target browser and animates correctly.

Related

  • JSPI-driven IO.sleep issue — the timing half of the same seam; the two together close the gap
  • #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."

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttoolingIssue around tooling built for the language (e.g. package managers, IDE plug-ins)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions