fluffos/testsuite/std
Claude f45461cb97 web terminals on xterm.js; TUI library fixes; emsdk pin; lws output-wedge fix
One coherent change: make the mudlib TUI library (/std/tui) work in the
browser on BOTH web terminals, harden everything the work surfaced, and
pin the toolchain that broke it.

## Web terminals: xterm.js + a shared telnet client

* Vendor @xterm/xterm 6.0.0 + @xterm/addon-fit 0.11.0 (dist files
  byte-exact from the official npm tarballs, licenses included) under
  src/www/vendor/. xterm.js does the terminal emulation on both pages:
  rendering, SGR 16/256/truecolor, alternate screen, cursor state, wide
  characters, scrollback, mouse reporting, bracketed paste, and keyboard
  encoding (the whole dialect testsuite/std/tui/keys.lpc decodes,
  including C-_ undo).
* src/www/telnet.js -- one telnet option engine shared by both pages
  (transport-agnostic; hooks for page-specific options): ECHO masks the
  password prompt, WILL/WONT SGA -- the driver's char-mode signal
  (set_charmode) -- automatically switches between the line-input bar
  and raw keystroke streaming, NAWS reports real terminal geometry from
  the fit addon and re-reports on resize (driving the window_size
  apply), TTYPE answers xterm-256color.
* src/www/wasm/index.html (the wasm shell): output/input rides xterm.js;
  the page keeps the synchronous-bridge queueing (sends flush outside
  receive() -- the wasm bridge re-enters the parser otherwise), the
  error modal, and the jsbridge handlers. Also guards
  crypto.getRandomValues() against views backed by resizable
  ArrayBuffers (see the emsdk section below).
* src/www/index.html (the websocket client for the native driver):
  rewritten on the same stack, replacing a parseANSI() that stripped all
  cursor sequences (no TUI possible) and a telnet layer whose option
  bytes leaked into the text stream and never answered negotiations.
  Passwords now mask, char mode works, GMCP/MSP kept (dead
  TelnetOverWebSocket/handler classes removed); ws frames arrive as
  arraybuffers (no Blob/FileReader path); UTF-8 and telnet sequences
  survive frame splits (streaming decode + stateful parser).
* tools/wasm/pack-mudlib.sh and the release zip ship vendor/ and
  telnet.js next to index.html in both layouts.

## Driver: websocket output wedged permanently on multi-window bursts

Re-arming the writeable event from inside LWS_CALLBACK_SERVER_WRITEABLE
is lossy with the libevent event lib: after the user callback returns,
lws core clears POLLOUT and its pollfd bookkeeping desyncs from the
evlib watcher -- the request is dropped and every later
lws_callback_on_writable() no-ops, freezing output on that connection
for good. First bites on any burst larger than one 2048-byte window
(e.g. a full-screen TUI frame; no test had ever pushed one through a ws
client). The ws_telnet.cc/ws_ascii.cc handlers now drain the evbuffer in
a loop gated on lws_send_pipe_choked(); a choked write is flushed by
lws's own core-managed POLLOUT path, which fires the callback again.
Found by the browser end-to-end run below; documented in AGENTS.md 14.

## TUI library (/std/tui): review fixes + features

Fixes: wslice() dropped combining marks from every sliced render;
ESC[1;mR (modified F3) misdecoded as a cursor position report; readline
lost the left scroll marker when a line overflowed both viewport edges;
stray mouse events cancelled incremental search; Tab on a unique
already-complete match missed the trailing space; menu lines wider than
the terminal wrapped and desynced the in-place repaint (width now fed by
the glue and re-fed on NAWS resize); the menu overflow indicator only
showed below the window; backward focus cycling from the initial state
skipped the last widget; a terminal-initiated close (disconnect,
tui_destroy) leaked the app clone and its widgets -- teardown now runs
through a reentry-guarded app_quit() in both directions.

Features (from the README's own deferred list): readline C-_ undo
(per-keystroke snapshots); pterm-style type-to-filter in select/
multiselect (results index the original choices); mouse-wheel scrolling
in list/table/tree/log (wheel no longer click-selects); table clicks
honour the header offset; tree Left on a leaf jumps to its parent.

All pinned by testsuite/single/tests/std/tui/fixes.lpc, including the
app teardown cycle via a runtime-written mock terminal.

## Toolchain: pin emsdk, guard random_get()

emsdk 6.0.2 defaulted GROWABLE_ARRAYBUFFERS=1, making every
ALLOW_MEMORY_GROWTH build's heap a resizable ArrayBuffer
(wasmMemory.toResizableBuffer()) in browsers shipping the wasm
rab-integration -- and two emscripten runtime paths pass raw
HEAPU8.subarray() views into Web APIs that reject resizable-backed
views: random_get() -> crypto.getRandomValues() (threw at boot) and
UTF8ToString() -> TextDecoder (broke jsbridge). 6.0.3 reverted the
default AND fixed the string codegen (getUnsharedTextDecoderView ->
getHeapViewOrCopy), but random_get() is still unguarded upstream.
Reproduced and certified against real 6.0.2/6.0.3 toolchains in
Chromium (--js-flags=--experimental-wasm-rab-integration): 6.0.2
unpatched throws the exact boot error, 6.0.2 + the page's
getRandomValues wrapper passes, 6.0.3 passes. CI now installs a pinned
emsdk-ver input (default 6.0.3) instead of "latest".

## Verification

* Native Debug+ASan: GTest 312/312; full LPC suite (574 files, per-file
  ref-count checker) x3 across the work; TUI test dir x3 randomized.
* LPC suite inside the wasm driver under node: 5274 checks, 574 files.
* Browser e2e (Playwright + Chromium): wasm shell 21/21 checks
  (charts/SGR, char-mode auto-switch, readline editing + undo +
  history, select with filter, multiselect/confirm, full-screen app,
  dashboard live repaint + NAWS resize relayout); websocket client
  against the native driver 13/13 twice on one instance (plus an
  ascii-subprotocol multi-window burst) -- the flow that caught the lws
  wedge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UvX1HbmGk9zWGBsW4Rkcq
2026-07-14 09:53:26 -05:00
..
fonts update stdlib, add bitmap_font 2020-03-10 08:57:53 -07:00
tui web terminals on xterm.js; TUI library fixes; emsdk pin; lws output-wedge fix 2026-07-14 09:53:26 -05:00
all_environment.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
base64.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
bitmap_font.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
break_string.h update break_string sefun 2020-03-11 21:53:17 -07:00
break_string.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
database.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
decimal.lpc std/decimal: exact fixed-point decimal arithmetic as an LPC library 2026-07-09 20:48:48 -04:00
element_of_weighted.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
highest.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
http.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
json.lpc json: buffer-based single-pass parser/encoder and comprehensive tests (#1163) 2026-07-12 02:02:49 -04:00
lowest.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
number_string.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
percent.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
present_clone.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
range.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
reduce.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
sum.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00
telnet.lpc Prefer .lpc source extension; rename testsuite to .lpc 2026-07-09 20:48:48 -04:00