Field notes
Sim REPL on OpenTUI (React)
Date: 2026-06-05
Replaced the hand-rolled raw-mode TUI (the keys/editor/screen/menu stack built earlier the
same day) with OpenTUI's React binding. ReplController and the sim run CI/transcript path are
unchanged — the React app is a new view over the controller's onOutput/onStatus streams.
Supersedes the same-day pinned input box work — those modules (custom key decoder, line editor, scroll-region painter, modal panels) are deleted. That finding stays as history; this is the live design.
What shipped#
tui/(React,@opentui/react):app.tsx(root:scrollboxoutput + status +input/overlays),picker.tsx(<select>for/layer·/as),help.tsx(scrollable<scrollbox>sheet),use-repl.ts(subscribe controller → state),mount.tsx(createRoot(createCliRenderer()).render).- Pure + unit-tested:
route.ts(submit → action),ansi.ts(forTextstrips ANSI for<text>). - Removed:
keys.ts,editor.ts,screen.ts,menu.ts,interrupt.ts+ their tests. Kept:repl.ts,render.ts,complete.ts,roles.ts,stub-agent.ts,transcript.ts. - Deps:
@opentui/core,@opentui/react,react,@types/react. tsconfigjsx: react-jsx; each.tsxcarries a/** @jsxImportSource @opentui/react */pragma (the binding augments JSX there, not globally).
Why OpenTUI / React#
Real flexbox + scrollbox + input/select with built-in focus/keyboard handling beat bespoke
ANSI bookkeeping. React, not Solid, because Bun transpiles plain JSX natively; Solid needs its
compile-time jsx-dom-expressions transform (extra Bun plugin plumbing). The heavier React dep is
irrelevant for a dev-only sim.
Spike findings (the gate) — see src/gateway/sim/tui/API-NOTES.md#
- Native lib loads and the React reconciler renders under Bun.
- The view is headlessly unit-testable via
@opentui/react/test-utilstestRender+waitForFrame+mockInput— soapp/picker/helphave real frame-assertion tests, not just manual smoke. <text>does not parse raw ANSI → strip with corestripAnsiSequences. Color is dropped in MVP; future enhancement: convert ANSI →StyledTextto restore it.<select>inside a borderless flex box collapses to 0 rows — give it an explicit height.exitOnCtrlCmayprocess.exitbeforeCliRenderEvents.DESTROY, somountTuialso disposes the controller on aprocess.once("exit")fallback.
Caveats (accepted)#
- OpenTUI is pre-1.0 ("not for production") + native FFI — fine for a dev-only sim;
bun run start(the Slack runtime) never imports it, so production images are unaffected. Pin the version. - Single-line
<input>flattens pasted newlines; multi-line authoring would need<textarea>(deferred). Color fidelity fromrender.tsis lost until the StyledText enhancement.
Verification#
bun test— 736 pass (route/ansi/view tests + ReplController transcripts).bun run typecheckclean.sim run(CI) — 25/25 transcripts, no TUI mounted.- Interactive TTY smoke is the remaining manual check (see checklist in the plan, Task 8).
Spec: docs/superpowers/specs/2026-06-05-sim-tui-opentui-design.md
Plan: docs/superpowers/plans/2026-06-05-sim-tui-opentui.md