Pass transient buffers to FFI calls as borrowed objects so Node keeps the JavaScript owner alive for the synchronous native read. Raw addresses can outlive the object that backs them, which lets GC corrupt styled text and cursor option data Fixes #1212
3.5 KiB
Agent Guidelines for opentui
Default to using Bun instead of Node.js.
- Use
bun <file>instead ofnode <file>orts-node <file> - Use
bun testinstead ofjestorvitest - Use
bun installinstead ofnpm installoryarn installorpnpm install - Use
bun run <script>instead ofnpm run <script>oryarn run <script>orpnpm run <script> - Bun automatically loads .env, so don't use dotenv.
NOTE: When only changing typescript, you do NOT need to run the build script. The build is only needed when changing native code.
APIs
Don't use bun-specific APIs. Generated code should work in Bun, Node.js and Deno runtimes.
Portable FFI Types
- In portable FFI code, stay within the
node:ffi/bun:ffitype intersection. - Avoid backend-specific ABI names in shared definitions: no
usize,napi_env, ornapi_value. Use explicit widths likeu32/u64. - Treat
i64/u64asbigint, and native booleans as0/1. - For pointer params backed by transient JavaScript memory, pass the
ArrayBufferor view directly; the FFI backend borrows it for the duration of the synchronous call. Never pre-resolve such arguments withptr()— a raw address carries no ownership and the runtime may collect the buffer before native code reads it. - Use
ptr(view)only for addresses serialized into structs or retained by native code, and keep the owning buffer alive for as long as native code can read the address. SharedPointervalues staynumber | bigint. - For C strings, encode to bytes and pass pointers; do not assume raw JS strings or portable native string return normalization.
- Create callbacks through the loaded library/platform facade, not
new JSCallback(...), and only assume same-thread callback behavior.
Testing
Use bun test to run tests from the packages directories for a specific package.
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
For more information, read the Bun API docs in node_modules/bun-types/docs/**.md.
Build/Test Commands
To build the project (before running typescript tests), run
bun run build
FROM THE REPO ROOT to make sure all packages are built correctly.
To run native tests for packages/core, run
bun run test:native
FROM THE packages/core DIRECTORY.
To filter native tests, use:
bun run test:native -Dtest-filter="test name"
FROM THE packages/core DIRECTORY.
Typescript Code Style
- Runtime: Bun with TypeScript
- Formatting: oxfmt (semi: false, printWidth: 120)
- Imports: Use explicit imports, group by: built-ins, external deps, internal modules
- Types: Strict TypeScript, use interfaces for options/configs, explicit return types for public APIs
- Naming: camelCase for variables/functions, PascalCase for classes/interfaces, UPPER_CASE for constants
- Error Handling: Use proper Error objects, avoid silent failures
- Async: Prefer async/await over Promises, handle errors explicitly
- Comments: Minimal comments, NO JSDoc
- File Structure: Index files for clean exports, group related functionality
- Testing: Bun test framework, descriptive test names, use beforeEach/afterEach for setup
Debugging
- NOTE this is a terminal UI lib and when running examples or apps built with it, you cannot currently see log output like console.log. Ask the user to run the example/app and provide the output.
- Reproduce the issue in a test case. Do NOT start fixing without a reproducible test case. Use debug logs to see what is actually happening. DO NOT GUESS.