4.3 KiB
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)
- Branch
chore/opentui-X.Y.Z, bump the three pins,npm ci. 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).- 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 3000vs current numbers (300–375MB / p99 6–8ms),--cell pipeline(frame pacing ≥22fps). - Shim audit: try each boundary shim OFF; delete the ones upstream fixed.
- Live tmux smoke (scroll sweep / resize / selection-copy), screenshots.
- 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
- Any plan to widen the 16-bit native handle table (or split per-kind)? That's our hard ceiling, independent of yoga.
- Is the Node
--experimental-ffipath on their support radar, or Bun-only? (Native yoga adds new FFI surface; we run Node.) - 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.