OpenTUI Agent Rules

A library for building terminal interfaces with TypeScript, Zig, React, and Solid.

Overview

OpenTUI's hardest rules sit between JavaScript runtimes and native terminal rendering. TypeScript, native, and distribution changes have different checks, while FFI types and memory ownership must work in both Bun and Node.

Documents

├── 
├── packages
│   └── web
│       └── 
└── terminal-controlterminal-control skill (installation-dependent)

Techniques in this file

01 / Hard prohibitions

Keep text measurements distinct

The guide prohibits treating byte counts, code points, graphemes, and terminal cells as interchangeable. Naming each unit makes the rule specific to terminal layout rather than a generic warning about strings.

Source excerpt starting at line 10.
- Do not interchange byte lengths, code points, graphemes, and terminal display-cell widths.

02

Make ownership a requirement on every exit path

Handles and callbacks are treated as resources that need explicit cleanup, alongside buffers and listeners. The same rule connects bounded input work with tests for lifecycle failures.

Source excerpt starting at line 8.
- Make native ownership explicit; clean up handles, callbacks, buffers, and listeners on every exit path. Bound  input-driven work and test lifecycle failures.

03 / Verification by change type

Choose checks by the boundary a change crosses

The verification section separates ordinary TypeScript edits from native and cross-package output changes. Runtime, FFI, build, and export changes also trigger Node and packaged-distribution checks.

Source excerpt starting at line 24.
- Ordinary TypeScript source changes do not require the root build. Use the affected package's `test`, `typecheck`,  `build`, or validation scripts as applicable.- Run `bun run build` from the repository root after native or cross-package build/output changes, or when tests report  a missing/stale native artifact. It does not build web or examples; use their package scripts.- For native changes, run `bun run test:native` from `packages/core`. Filter with `bun run test:native -Dtest-filter="test name"` while iterating.- For runtime, FFI, build, or export changes, run the relevant Node and packed-distribution scripts from that package  (such as `test:js:node` or `test:dist`) when present.

04

Use a shared FFI contract

The portable-signature rule names backend-only types to avoid and the JavaScript representations to use instead. It gives contributors a concrete contract for crossing the Bun and Node boundary.

Source excerpt starting at line 40.
- Portable symbol signatures must stay within the `node:ffi`/`bun:ffi` intersection. Use explicit widths such as  `u32`/`u64`, not backend-only ABI names such as `usize`, `napi_env`, or `napi_value`; represent `i64`/`u64` as `bigint`,  native booleans as `0`/`1`, and shared pointers as `number | bigint`.

05

Stabilize storage before taking its address

A retained native pointer requires stable backing storage and a live owner. The guide gives the required operation order and explains how reversing it can invalidate the address.

Source excerpt starting at line 52.
- Use `ptr(view)` only when native code stores the address beyond the call. Before resolving it, access `view.buffer` to  move any inline typed-array storage into a stable `ArrayBuffer`, then keep the view alive for the complete native  lifetime. The order is required: `const owner = view.buffer; const address = ptr(view)`. Calling `ptr(view)` first and  accessing `view.buffer` later can move the storage and invalidate `address`.

Ideas for your repo

  1. Name the distinct units that must never be substituted for one another.
  2. Map verification to runtime, native, and distribution boundaries.
  3. Document native ownership and cleanup alongside API signatures.
  4. Explain ordering constraints with the failure caused by reversing them.
Sponsored byModem

Give your agents the whole story.

These instructions explain how to work in OpenTUI. Modem shows your agents what customers said, who is affected, and what changed.

See how Modem works