Summary
Vera's "write once, run anywhere" framing has a real seam at the IO boundary that's not currently surfaced in user-facing documentation. A program written for terminal-target ergonomics (using IO.sleep for animation timing and ANSI escape codes for cursor control) compiles cleanly to --target browser but doesn't run meaningfully there: IO.sleep busy-waits and freezes the tab, and ANSI escapes render as literal text in the DOM. The right shape for the browser is "Vera as pure simulation core, JS as timing+rendering driver" — but this isn't documented anywhere, and the runtime gaps that would make the JS-driver pattern ergonomic (#603) aren't filled in either.
Concrete manifestation
An agent writing Conway's Life on current main:
- Wrote a perfectly reasonable terminal-target program (
IO.sleep(100) between frames + ANSI \u{1B}[2J\u{1B}[H clear+home).
- Compiled it cleanly with
vera compile --target browser.
- Tried to load the bundle and observed: tab frozen for 30 seconds, output appearing as raw escape codes in DOM rather than animated grid.
- Wrote a separate browser-shaped variant: pure simulation core in Vera, JS driver using
requestAnimationFrame and <canvas>.
- Hit #603 (no string-marshalling helpers exposed), worked around with a "compute-everything-upfront, drain stdout once" pattern.
This is a real adoption surface: the message "Vera is write-once-run-anywhere" sets up an expectation that the same source runs identically on both targets. The seam at IO breaks that expectation in a way that isn't a bug — it's a runtime-shape mismatch that no language can fully hide — but the documentation should set the expectation correctly.
Two architecture-level options
Option 1 (recommended): embrace the seam, document it explicitly
- Add a "Browser target: limitations and patterns" section to SKILL.md and the spec
- Explicitly call out: terminal animations using
IO.sleep + ANSI escapes don't translate; the browser runtime expects "Vera = pure simulation core, JS = timing/rendering" and that the IO surface for the browser is IO.print (writes to the page) only
- Provide a worked example (Conway's Life is a candidate) showing the JS-driver pattern
- Pair with #603 to make the marshalling ergonomic
Option 2: bridge the seam in the runtime
- Extend the browser runtime to interpret ANSI escapes (clear-screen, cursor-home, EOL-erase, plus colour codes) into DOM mutations
- Replace
IO.sleep's busy-wait with a requestAnimationFrame-aware yield that doesn't block the main thread
- Significantly bigger lift; would let the same source run on both targets
Option 1 is honest about Vera's design point ("pure core + effects at the boundary, the boundary differs between targets"). Option 2 papers over a real distinction with engineering work that may be misplaced — agents writing for a browser target arguably should think differently about timing and rendering than agents writing for a terminal.
Acceptance for option 1
- SKILL.md has a "Browser target" subsection explaining the IO model split, with concrete examples of:
- what works (
IO.print rendering, pure functions called from JS)
- what doesn't (
IO.sleep blocks main thread, ANSI escapes render as text)
- the recommended pattern (Vera pure core + JS driver via
requestAnimationFrame)
- The "write once, run anywhere" claim in README.md is qualified to acknowledge the IO boundary
Related
- #603 — the runtime gap that would make option 1's recommended pattern ergonomic
- #604 — the prelude-skipped issue, also browser-target-specific
Summary
Vera's "write once, run anywhere" framing has a real seam at the IO boundary that's not currently surfaced in user-facing documentation. A program written for terminal-target ergonomics (using
IO.sleepfor animation timing and ANSI escape codes for cursor control) compiles cleanly to--target browserbut doesn't run meaningfully there:IO.sleepbusy-waits and freezes the tab, and ANSI escapes render as literal text in the DOM. The right shape for the browser is "Vera as pure simulation core, JS as timing+rendering driver" — but this isn't documented anywhere, and the runtime gaps that would make the JS-driver pattern ergonomic (#603) aren't filled in either.Concrete manifestation
An agent writing Conway's Life on current main:
IO.sleep(100)between frames + ANSI\u{1B}[2J\u{1B}[Hclear+home).vera compile --target browser.requestAnimationFrameand<canvas>.This is a real adoption surface: the message "Vera is write-once-run-anywhere" sets up an expectation that the same source runs identically on both targets. The seam at
IObreaks that expectation in a way that isn't a bug — it's a runtime-shape mismatch that no language can fully hide — but the documentation should set the expectation correctly.Two architecture-level options
Option 1 (recommended): embrace the seam, document it explicitly
IO.sleep+ ANSI escapes don't translate; the browser runtime expects "Vera = pure simulation core, JS = timing/rendering" and that the IO surface for the browser isIO.print(writes to the page) onlyOption 2: bridge the seam in the runtime
IO.sleep's busy-wait with arequestAnimationFrame-aware yield that doesn't block the main threadOption 1 is honest about Vera's design point ("pure core + effects at the boundary, the boundary differs between targets"). Option 2 papers over a real distinction with engineering work that may be misplaced — agents writing for a browser target arguably should think differently about timing and rendering than agents writing for a terminal.
Acceptance for option 1
IO.printrendering, pure functions called from JS)IO.sleepblocks main thread, ANSI escapes render as text)requestAnimationFrame)Related