Vera version: vera 0.0.138
Origin: Friction surfaced while writing terminal Tetris on Vera 0.0.138.
Problem
Single-character input is currently impossible at any target. IO.read_line is line-buffered, so real-time CLI programs (Tetris-class games, paced REPLs, navigation tools that respond to keystrokes) can't be written in Vera at all — neither for terminal nor for browser.
Proposal
Single new operation in the existing IO effect:
effect IO {
...
op read_char(Unit -> @Result<String, String>);
...
}
Returns one character (as a one-char String) or an Err on EOF / closed input. The runtime handles raw-mode entry/exit transparently per call — no separate set_raw_mode operation in the user-facing API.
Host implementations
- Python wasmtime host (
vera/codegen/api.py): termios.tcsetattr context manager on Unix (~5 lines, stdlib), msvcrt.getch on Windows (no separate raw-mode call). Handle EOF / interrupt cleanly.
- Browser runtime (
runtime.mjs): a keypress event listener pushes characters into a queue; read_char either pops the head or suspends-and-resumes via JSPI on the next keypress. Same suspend/resume primitive that #609 uses for IO.sleep.
What this unlocks
Real-time CLI programs that compile cleanly to either target with no source changes. Pairs with:
- #609 —
IO.sleep portable across targets (timing)
- #610 — ANSI subset interpreter in browser (rendering)
Together those three close the input/timing/rendering trio for write-once-run-anywhere real-time programs (#608 umbrella).
Out of scope (separable filings)
set_raw_mode — explicit lifecycle control over raw mode. The implicit-per-call approach above covers the common case.
enable_alternate_screen — useful but not blocking; can be filed alongside #610.
get_size — (rows, cols) from ioctl(TIOCGWINSZ) / GetConsoleScreenBufferInfo / window.innerWidth. Useful but separable.
Design alignment
DESIGN.md commits to WebAssembly both native and browser as first-class targets:
Target: WebAssembly (native + browser) — Portable, sandboxed, no ambient capabilities; vera run uses wasmtime; vera compile --target browser emits a JS bundle
A capability available on only one target violates that commitment. Single-character input has to be portable just as #609 made IO.sleep portable — both directions of the terminal-vs-browser seam need symmetric treatment.
Adding IO.read_char as a single new operation in the existing IO effect (rather than a new <Terminal> effect with multiple operations) follows precedent: IO.sleep, IO.time, and IO.stderr were added to existing IO in v0.0.114 (issue #463) rather than spun off into a new effect. Smaller surface, fewer effect labels, same expressivity.
Origin
Friction document from the terminal Tetris experiment.
Vera version:
vera 0.0.138Origin: Friction surfaced while writing terminal Tetris on Vera 0.0.138.
Problem
Single-character input is currently impossible at any target.
IO.read_lineis line-buffered, so real-time CLI programs (Tetris-class games, paced REPLs, navigation tools that respond to keystrokes) can't be written in Vera at all — neither for terminal nor for browser.Proposal
Single new operation in the existing
IOeffect:Returns one character (as a one-char
String) or anErron EOF / closed input. The runtime handles raw-mode entry/exit transparently per call — no separateset_raw_modeoperation in the user-facing API.Host implementations
vera/codegen/api.py):termios.tcsetattrcontext manager on Unix (~5 lines, stdlib),msvcrt.getchon Windows (no separate raw-mode call). Handle EOF / interrupt cleanly.runtime.mjs): a keypress event listener pushes characters into a queue;read_chareither pops the head or suspends-and-resumes via JSPI on the next keypress. Same suspend/resume primitive that #609 uses forIO.sleep.What this unlocks
Real-time CLI programs that compile cleanly to either target with no source changes. Pairs with:
IO.sleepportable across targets (timing)Together those three close the input/timing/rendering trio for write-once-run-anywhere real-time programs (#608 umbrella).
Out of scope (separable filings)
set_raw_mode— explicit lifecycle control over raw mode. The implicit-per-call approach above covers the common case.enable_alternate_screen— useful but not blocking; can be filed alongside #610.get_size—(rows, cols)fromioctl(TIOCGWINSZ)/GetConsoleScreenBufferInfo/window.innerWidth. Useful but separable.Design alignment
DESIGN.mdcommits to WebAssembly both native and browser as first-class targets:A capability available on only one target violates that commitment. Single-character input has to be portable just as #609 made
IO.sleepportable — both directions of the terminal-vs-browser seam need symmetric treatment.Adding
IO.read_charas a single new operation in the existingIOeffect (rather than a new<Terminal>effect with multiple operations) follows precedent:IO.sleep,IO.time, andIO.stderrwere added to existingIOin v0.0.114 (issue #463) rather than spun off into a new effect. Smaller surface, fewer effect labels, same expressivity.Origin
Friction document from the terminal Tetris experiment.