Files
hermes-agent/docs/opentui-upstream-alignment.md

4.3 KiB
Raw Blame History

Upstream alignment — how we inherit OpenTUI's performance work for free

Context (maintainer, 2026-06-11): opencode's 100-message cap was a November-era performance workaround, since obsoleted; the next OpenTUI version ships native yoga (≥2× layout performance, more improvements building on it); opencode does not use virtualization.

The invariant that makes alignment free

We are forkless and public-API-only. The windowing layer (S1+S2) drives the STOCK <scrollbox> through documented surface only — onSizeChange, setFrameCallback, scrollTop/viewport/scrollHeight, Solid <Show> mount/unmount. Zero patches to @opentui/core. Every upstream release therefore drops in by bumping three pinned versions in ui-opentui/package.json (@opentui/{core,keymap,solid}, currently 0.4.0). Keep it that way: any new code that needs core behavior goes through a boundary/ wrapper, never a patched dependency.

What native yoga changes for us (and what it doesn't)

  • Kills the WASM ratchet (grow-only linear memory → freeable native allocations). This retro-justifies S2 less, but S2's append-time windowing remains correct: transient mounted peaks still cost handles and RSS.
  • Does NOT obsolete windowing. The binding constraint is the 65,535-slot native handle table: ~47 handles/row × 3,000 stored rows ≈ 141k handles — over the table at ANY layout speed. Windowing is what makes the 3,000-row scrollback possible; yoga's backend is irrelevant to that math.
  • Makes windowing feel even better: 2× layout = cheaper margin remounts = smaller window margins viable and less exposure for the one accepted limit (estimate-height snap under scrollbar jumps). After the bump, re-tune margin/ hysteresis against the scroll cell.

The shim ledger (delete-on-upstream-fix; all in ui-opentui/src/boundary/)

shim what it papers over delete when
ffiSafe.ts u32 draw coords go negative under Node FFI (Bun silently wraps) — ERR_INVALID_ARG_VALUE loop upstream clamps, or Node FFI path is officially supported
nativeHandles.ts SyntaxStyle exhaustion crashes mid-mount; degrade-to-unstyled handle table widened (INDEX_BITS>16) or per-kind tables
renderer.ts exit-signal guard core 0.4.0 treats SIGPIPE (clipboard spawn) as an exit signal; its own uncaughtException handler allocates a handle and dies (exit-7 masking) both fixed upstream
clipboard.ts hardening same SIGPIPE incident class with the above

Each is (a) isolated, (b) inert if upstream fixes the behavior, (c) worth reporting upstream — four concrete, reproduced, root-caused issues. Filing them is the cheapest alignment lever we have: it converts our workarounds into upstream regression tests. (Needs glitch's go-ahead — public repo activity.)

The upgrade playbook (per upstream release)

  1. Branch chore/opentui-X.Y.Z, bump the three pins, npm ci.
  2. npm run check (648 tests; the windowing invariants — identical scrollHeight ON/OFF, byte-stable frames across corrections — are literal assertions and will catch behavioral drift).
  3. Bench acceptance, sequential: --cell gate (determinism digest; EXPECT a new digest if upstream changed rendering — eyeball the frame, re-bless), --cell mem3000 --msgs 2000 + --cell scroll --msgs 3000 vs current numbers (300375MB / p99 68ms), --cell pipeline (frame pacing ≥22fps).
  4. Shim audit: try each boundary shim OFF; delete the ones upstream fixed.
  5. Live tmux smoke (scroll sweep / resize / selection-copy), screenshots.
  6. Windowing re-tune if layout got faster: margins up or hysteresis down, re-run scroll cell, keep p99 ≤ 17ms gate.

The bench suite IS the upgrade contract — it's exactly the harness that lets us take every upstream improvement within a day of release, with proof.

Questions worth relaying to the maintainer

  1. Any plan to widen the 16-bit native handle table (or split per-kind)? That's our hard ceiling, independent of yoga.
  2. Is the Node --experimental-ffi path on their support radar, or Bun-only? (Native yoga adds new FFI surface; we run Node.)
  3. Would they take the windowing layer's core-agnostic pieces (exact-height spacer pattern, correction-legality rule) as a documented recipe or framework-level utility? We have it production-shaped with tests.