The native viewport lookup reports whether a view exists, but the FFI
binding discarded that result. Preserve the boolean so callers can
distinguish missing views from successful lookups.
A backpressured frame could schedule a retry after its callback paused
or
stopped the renderer, reviving a cancelled continuous loop.
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
## Summary
- recognize the selected Ghostty `std.log` scopes in OpenTUI's root
logger
- keep all standard-library logs silent by default
- read `OTUI_GHOSTTY_LOG_LEVEL` inside the native `logFn` and forward
enabled messages through the existing `logger.logMessage` path
- support `error`/`err`, `warn`/`warning`, `info`, and `debug`
thresholds
- document the environment variable
## Behavior
Unset or invalid `OTUI_GHOSTTY_LOG_LEVEL` values emit no `std.log`
messages. Recognized Ghostty scopes are forwarded up to the configured
severity. Logs from every other scope are discarded. Explicit OpenTUI
logger calls are unchanged.
## Testing
- release `bun run build` from `packages/core`
- `bun run test:native` from `packages/core` (2066 passed, 8 skipped)
- `zig fmt --check packages/native/src/lib.zig
packages/native/src/logger.zig`
- `oxfmt --check packages/web/src/content/docs/reference/env-vars.mdx`
## Summary
- install a root `std_options.logFn` that discards `std.log` output
- leave Zig's default compile-time log level unchanged
- keep explicit OpenTUI `logger.logMessage` output flowing through the
existing JavaScript callback
## Why
OpenTUI imports native dependencies such as Ghostty into its Zig root
module. Their `std.log` calls otherwise use Zig's default stderr logger,
bypass the renderer, and can corrupt the terminal display. OpenTUI
already has a separate explicit logger for messages intended for
JavaScript and file logging, so standard-library logs should not write
directly to the host terminal.
## Testing
- `zig fmt --check packages/native/src/lib.zig`
- release `bun run build` from `packages/core`
- `bun run test:native` from `packages/core` (2066 passed, 8 skipped)
## Summary
- reject parser escape codes such as `[A` and `[27u` as physical key
identifiers
- infer Web-style physical identities (`ArrowUp`, `KeyC`, `Digit1`,
`Escape`) before calling Ghostty
- derive the unshifted codepoint from that normalized physical identity
- cover raw arrow input and nested Kitty character/escape re-encoding
## Why this is needed
OpenTUI key events use `code` for two different kinds of values. Kitty
and raw escape parsing can produce parser codes such as `[A` for Up or
`[27u` for Kitty Escape, while other events provide Web-style physical
codes such as `KeyA`. Ghostty's key encoder accepts the latter physical
identities, not parser escape fragments. Passing `[A` through as the
physical key leaves Ghostty unable to encode an arrow key.
Embedded terminals also need to re-encode input when the program inside
them enables the Kitty keyboard protocol. In that mode, text alone is
insufficient for modified and disambiguated keys: Ghostty needs a
normalized physical identity and its unshifted codepoint. Raw character
events may not include `code`, so this change infers `KeyA`/`Digit1`;
Kitty escape events whose parser code is `[27u` fall back to `Escape`.
The regression verifies that Ctrl+C and Escape are re-emitted as Kitty
sequences for a nested terminal.
## Testing
- `bun test src/renderables/EmbeddedTerminal.test.ts` (16 passed)
- `oxlint packages/core/src/renderables/EmbeddedTerminal.ts
packages/core/src/renderables/EmbeddedTerminal.test.ts`
- `oxfmt --check packages/core/src/renderables/EmbeddedTerminal.ts
packages/core/src/renderables/EmbeddedTerminal.test.ts`
Ghostty 1.3 renders certain multi-code-point graphemes two columns wide.
Use its width profile for direct sessions so rendering, wrapping, and
cursor placement stay aligned with the terminal.
Fix#1401
Thai SARA AM occupies an additional cell within a grapheme. Count
positive-width spacing marks to keep output coordinates aligned with
terminals.
Fix#479
## Summary
- correlate cursor-position reports with the two emitted OSC 66 probes
- keep the startup cursor report out of capability detection
- require the exact expected columns instead of accepting larger echoed
payload widths
Terminals that echo an unsupported OSC 66 payload can leave the cursor
well past the expected probe column. Treating any row-1 report at or
beyond the expected column therefore enables capabilities the terminal
does not support. The probe state now consumes only the two ordered
responses emitted by `queryTerminalSend`, while an unrelated cursor
report cannot enable text scaling.
Closes#1383.
## Testing
- filtered Zig 0.16 native tests: 2 passed
- full Zig 0.16 native suite: 2,013 passed, 8 skipped
- native hello example
- `bun run build`
- `bun run fmt:check`
- `bun run lint`
- `zig fmt --check packages/native/src/terminal.zig
packages/native/src/tests/terminal_test.zig`
---------
Co-authored-by: Simon Klee <hello@simonklee.dk>
Model native flags as numeric bytes so Bun and Node share one FFI
contract. Keep split-footer commits within Darwin ARM64's eight argument
registers to prevent stack marshalling from losing frame-finalization
state.
Fix https://github.com/anomalyco/opentui/issues/74
Double-click selects a word. Triple-click selects the line. A first
click still selects nothing.
Renderer counts left presses. A second press on the same cell within 500
ms asks for a word. A third press asks for a line.
Drag after a word click keeps the spaces between words. Shift+arrow
after a multi-click extends by cell, not by word.
ASCII font and tables stay cell-only. The click interval and the
word-character set are hard-coded for now.
Replace Commit Mono with a project-owned Iosevka build so the site
typeface can evolve with OpenTUI's visual identity. Add a private font
lab to compare future builds before publishing them.
This fixes the selection breaking when `x` is before the text, but `y`
below it. Without the fix, the show selection is flipped.
---------
Co-authored-by: Ofek Doitch <Ofek@WeedonAndScott.com>
Co-authored-by: Simon Klee <hello@simonklee.dk>
Selection combines half-open forward ranges with an inclusive backward
workaround. Replaying a local selection can change its range, and
extending
by one display column can split a wide grapheme.
Add an explicit occupancy model. Cell occupancy includes both endpoint
graphemes for Vim-style behavior. Boundary occupancy uses a half-open
range between insertion points. This behavior matches conventional thin
cursors.
Keep occupancy independent of cursor paint. Normalize endpoints to whole
graphemes. This keeps highlighting, copied text, deletion, cursor sync,
and wrapped-line navigation consistent in both directions.
A press without movement keeps the selection empty. Preserve half-open
offset selections. A cursor at the end of a line does not occupy the
newline, but crossing the line boundary selects it.
Supersedes the boundary-semantics direction of PR
https://github.com/anomalyco/opentui/pull/1389.
## Summary
- use `buffer` ABI parameters and pass typed-array owners directly for
transient synchronous Bun FFI calls
- lower portable `buffer` parameters to Node's stable `pointer` path
while still passing owner objects directly
- keep `ptr` only for nullable, mixed native-pointer, raw `ArrayBuffer`,
callback, and retained-memory cases
- stabilize retained text-memory views through `.buffer` before
resolving their native address
- preserve raw-pointer compatibility while allowing direct buffers in
supersample, packed-buffer, matrix, and grayscale APIs
- document the Bun 1.3.14 and Bun 1.4+ ownership rules in `AGENTS.md`
## Why
A Bun `FastTypedArray` may store its data inline. Calling `ptr(view)`
captures that address, but a later first access to `view.buffer` can
move the data into separate `ArrayBuffer` storage and leave the native
address stale. Passing the owner directly lets the FFI backend borrow
the current storage for synchronous calls and allows Bun's `buffer` fast
path to keep the owner visible to the JIT.
This keeps nullable and true native-pointer parameters on `ptr`, where
`buffer` cannot represent the ABI, while avoiding pre-resolved addresses
for transient memory. Retained pointers explicitly materialize stable
backing storage before `ptr(view)` and keep the view alive for the
native lifetime.
Node 26.4's Linux optimized `buffer` trampoline delivered a null pointer
to a multi-argument audio call in CI. OpenTUI therefore maps its
portable `buffer` descriptor to Node `pointer`, whose documented
owner-borrowing path passes the same views correctly. Bun continues to
receive the `buffer` descriptor.
Related Bun investigation: oven-sh/bun#32054 and oven-sh/bun#32055.
## Testing
- `bunx bun@1.3.14 test
src/tests/ffi-borrowed-pointer-callsites.test.ts` (23 passed)
- `bun test src/tests/ffi-borrowed-pointer-callsites.test.ts` on Bun 1.4
canary (23 passed)
- `bun run test:js` (5,397 passed, 23 skipped)
- `bun run test:js:node` with Node 26.4.0 (4,665 passed, 6 skipped)
- `bun run test:dist`
- `bun run build:lib`
- `bun run fmt:check`
- `bun run lint`
Make native builds use a pinned local archive instead of fetching
Zig dependencies from remote package servers. This removes network
availability from CI and makes the supplied source inputs reproducible.
Separate the native implementation from the TypeScript bindings. Keep
core on
the public bindings and keep the current native artifact packages and
release
workflows.
packages/native owns the Zig source, tests, benches, and vendor updates.
The
package is private and unpublished for now Core still packages the
@opentui/core-* artifacts. build:native, test:native, and bench:native
still
work from core and delegate to native.
Expose a Zig import root and a no-JS hello example for proof of concept,
not a focus point, just a smoke test.
Screen handles DCS payloads differently from tmux and consumes the inner
ST, leaving the outer terminal with an unterminated OSC 52 request. Use
BEL and bounded payload framing so Screen forwards complete clipboard
sequences, including large values.
Reference: https://github.com/anomalyco/opencode/issues/28590
The terminal can send mouse reports after OpenTUI releases stdin.
Clear pending input during shutdown. This stops the next app from
reading stale escape sequences.
Keep input received after suspend(). The app owns terminal input then.
Close#1360