Skip to content

fix(deps): update dependency @opentui/core to v0.7.0 - #465

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/opentui-core-0.x
Open

renovate[bot] wants to merge 1 commit into
masterfrom
renovate/opentui-core-0.x

Conversation

@renovate

@renovate renovate Bot commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
@opentui/core (source) 0.5.10 → 0.7.0 age confidence

Release Notes

anomalyco/opentui (@​opentui/core)

v0.7.0

Compare Source

Before 0.7.0, typing one character in a 10,000-line Textarea laid out all 10,000 lines before the key call returned, and the Yoga measure of the next frame walked them again. Typing and pasting slowed down with the size of the document, and word wrap paid the most. In 0.7.0, an edit only records that the view must scroll to the cursor. The layout runs once, at the next read of the viewport or the visual cursor, or at the next frame. It compares the text with the text of the last layout and lays out only the lines around the change. An insert, a delete, an undo, or a join lays out one line. Enter lays out two lines, and a 100-line paste lays out 101.

A read that needs the layout still pays for it. visualCursor lays out the text when an edit is pending. cursorOffset reads the logical cursor and does not lay out the text. To check after each key whether the cursor is at the start of the text, compare cursorOffset:

const atStart = input.cursorOffset === 0 // not input.visualCursor.offset === 0

Before 0.7.0, Renderable found the hooks of each node by reflection. It scanned the property descriptors of every new instance two times. For 10,000 rows of a Text subclass that overrides renderSelf, this took 20,000 scans and 160,000 descriptor reads. Now a node reads its hooks from its class once, at construction. Prototype methods with super, constructor options, and assignments to renderBefore, renderAfter, onLifecyclePass, and onSizeChange work as before.

Three kinds of code must change. First, a hook that you add by reflection does nothing until you call node.refreshHooks(). Examples are an instance class field, Object.defineProperty(), or an assignment to the renderSelf or onUpdate of one node. Second, a patch or spy on a prototype method after nodes exist does not reach those nodes, and untyped JSX props such as onUpdate and onResize have no effect. Third, renderBefore, renderAfter, and onLifecyclePass are now accessors. A class field that overrides one of them is a TypeScript error, so assign the hook in the constructor. An onLifecyclePass class field still runs, because attaching the node registers it.

import { BoxRenderable, RGBA, type BoxOptions, type RenderContext } from "@opentui/core"

class MarkedBox extends BoxRenderable {
  constructor(ctx: RenderContext, options: BoxOptions) {
    super(ctx, options)
    this.renderAfter = (buffer) => {
      buffer.drawText("*", this.screenX, this.screenY, RGBA.fromInts(255, 0, 0))
    }
  }
}

A class that used defineNativeIntegration() must now declare the description directly. Without the type annotation, kind does not keep its literal type:

import { Renderable, type NativeRenderableIntegration } from "@opentui/core"

class Marker extends Renderable {
  static override readonly nativeIntegration: NativeRenderableIntegration = {
    kind: "custom",
    body: "host",
  }
}

The nativeSceneWorkBudget renderer option let a frame yield to the event loop during native preparation. OpenTUI, its SSH package, and OpenCode did not set it. On a 10,000-line log, it cut the longest native slice by about 0.15 ms but used 27% more native time. With one mutation per event-loop turn, each yield started preparation again, until the frame failed with LayoutLimit. A renderer that scrolled or streamed under a budget showed no frames. Now preparation and paint finish in one call. A C binding built against the old header passes every argument after the removed max_work_items one slot off, so the ABI version changes. To update a binding, generate it again from the new opentui.h. Then remove the max_work_items argument and any OT_SCENE_FRAME_YIELD case, and use the 224-byte ot_scene_stats record. The work_budget of ot_session_pump is a different budget and does not change.

0.7.0 also adds a native input parser. The TypeScript parser parses each framed unit again with regular expressions, and its structure caused bugs that are hard to fix in place. These bugs include lost Escape-prefixed events, empty Alt key names, wrong Alt keys for non-ASCII characters, wheel releases that ended drags, and negative X10 coordinates. The native parser is a bounded state machine that does not allocate, and it handles all of these cases. It is off by default while timing and buffering edge cases are tested. To try it, set OTUI_NATIVE_INPUT_PARSER=1 or create the renderer with the option:

const renderer = await createCliRenderer({ experimental_nativeInput: true })

With OTUI_NATIVE_INPUT_SHADOW=1, the renderer also runs the other parser. It writes a [stdin-shadow] warning for each event that the two parsers report differently. The warnings contain your input, so protect them as you would a stdin log. To report a parser bug, include a log from OTUI_STDIN_LOG. The native parser is bounded, so it ignores stdinParserMaxBufferBytes.

Known issue: when the terminal hangs up, as when an SSH connection closes, the renderer prints Error disabling raw mode and Native scene shutdown failed errors with stack traces to stderr. The process still exits with code 0.

Breaking changes
  • core: A renderer created with nativeSceneWorkBudget throws at construction, so remove the option. NativeSceneFrame.Yield and the maxWorkItems argument of FFIRenderLib.sceneFrameStep() are removed. See How Core uses native. (#​1719)
  • core: NativeSceneFrameRequest.geometryRevision is removed. See How Core uses native. (#​1760)
  • native: ot_scene_frame_step_with_geometry no longer has its max_work_items parameter, OT_SCENE_FRAME_YIELD is removed, and OT_CONTEXT_ABI_VERSION is 2. Build bindings again from the new opentui.h, because records with version 1 fail with OT_UNSUPPORTED_VERSION. See C. (#​1719)
  • native: ot_scene_stats changes from 64 to 224 bytes, and ot_scene_get_stats returns OT_INVALID_ARGUMENT for the 64-byte record. Generate the record again from the new header. See Frames and output. (#​1722)
  • native: In the Zig module, Context.sceneFrameStepWorkBudgeted is removed, sceneFrameStepWithRecording no longer takes max_work_items, and replaceOwnedStyledText no longer takes a SyntaxStyle. OwnedStyledChunk now holds a style value, and the module no longer exports 12 extra span-feed symbols. See Zig. (#​1719, #​1768, #​1784)
  • core: Renderable.defineNativeIntegration() and NativeRenderableIntegration.construction are removed. Declare static override readonly nativeIntegration: NativeRenderableIntegration = { ... } without construction. See Custom renderables. (#​1754)
  • core: A node reads its hooks from its class at construction, so a hook added by reflection runs only after refreshHooks(). A class field that overrides renderBefore, renderAfter, or onLifecyclePass is a TypeScript error (TS2610), so assign it in the constructor. See Custom renderables. (#​1754)
  • core: The protected TextBufferRenderable._textBufferSyntaxStyle field is removed. Styled text keeps its styles in the text buffer, so remove code that reads the field. See Code. (#​1775)
  • core: translateX and translateY throw RangeError for values outside the 32-bit float range of the layout setters. Before, such values did not paint. See Layout. (#​1682)
Added
  • core: The experimental_nativeInput renderer option, or OTUI_NATIVE_INPUT_PARSER, parses stdin with a new native parser. OTUI_NATIVE_INPUT_SHADOW runs both parsers and logs each event that they report differently. See Environment variables. (#​1592, #​1787)
  • native: ot_session_input_feed() parses terminal input into caller-owned ot_input_event records, a payload buffer, and an ot_input_drain summary, and it does not allocate. ot_session_input_expect() sets the replies that the parser expects, and ot_session_input_reset() clears the parser state. FFIRenderLib.sessionInputFeed(), sessionInputExpect(), and sessionInputReset() wrap them. See Host I/O and time. (#​1592)
  • core: getNativeStats().frameWork, also in getStats(), counts the work of the last presented scene frame, such as Yoga solves, view preparations, and painted cells. The test renderer option nativeSceneFullFrame makes each frame rebuild its work lists, so tests can compare the counts. See Rendering diagnostics. (#​1722)
  • native: ot_scene_stats reports the work of the last presented scene frame. ot_scene_set_debug_flags() with OT_SCENE_DEBUG_FULL_FRAME forces the full frame path, and full_path_reasons uses the OT_SCENE_FULL_PATH_* bits. See Frames and output. (#​1722)
  • core: The protected _paintWidth and _paintHeight of Renderable give the size that a renderSelf() paints, clamped to the cell range that drawing accepts. See Custom renderables. (#​1682)
Changed
  • core: An edit in a text view or editor lays out only the lines that it changed, once, at the next read or frame. Typing one character in a 10,000-line Textarea lays out 1 line, not 10,000. Typing or pasting over a selection scrolls once instead of twice. See Editing buffers and views. (#​1758, #​1751, #​1747)
  • core: A Yoga measure of text at the render width reads the render layout, and at a different width it measures again only the edited lines. One edit in a 10,000-line word-wrapped editor measures 0 lines, not 10,000. See Layout. (#​1772)
  • core: Text writes that cannot change the size no longer cause a Yoga solve. Examples are the same bytes, colors, styles, viewport changes, scrolls, textAlign, truncate, tabIndicator, tabIndicatorColor, and the editor scroll margin. See Rendering pipeline. (#​1755, #​1767, #​1774)
  • core: A ScrollBox with viewport culling refreshes its rows only after a layout change or a row change, and an idle ScrollBox requests no update hook. A 10,000-row list does 0 row refreshes on idle and scroll frames, not 10,000. See ScrollBox. (#​1756, #​1761)
  • core: A frame records one hit rectangle per member and no longer writes a screen-sized hit grid. A hit test walks the members of the presented frame from the top. See Interaction, focus, and selection. (#​1771)
  • core: With wrapMode: "none", a line that is scrolled right skips the text to the left of the viewport by chunk and cluster. An idle editor with one 256 KiB line no longer walks the line on each frame. See Textarea. (#​1777)
  • core: After style and paint writes, reads of x, y, width, and height use the cached layout without a native call or a flush. getLayout() and getComputedLayout() call native code on every call, and a rejected staged write shows at the next flushing call. See Layout. (#​1760)
  • core: Only native code checks staged style writes. On Node, a box.width write with a flush takes 111 ns instead of 149 ns. A paint value that only native code rejects, such as an opacity above 1 passed to setNativeScenePaint, fails at the next flush. See Rendering pipeline. (#​1770)
  • core: A text buffer keeps its styled-text styles by value, and a Code creates two native objects instead of three. A highlight update no longer copies the style table or scans the object table. See Code. (#​1768, #​1775)
  • core: A wheel step scrolls only the innermost scroller under the pointer that can still move in its direction. A scroller at its edge, or one that cannot scroll that axis, gives the step to the next scroller out, as browsers do. Before, one step moved every scroller on its path, so the text of a Textarea inside a ScrollBox moved twice as far, and the Textarea moved away from the pointer. The event still bubbles, so the split-pane sync of a Diff and the onMouseScroll handler of an ancestor still receive it. Text, Code, and Textarea treat Shift+wheel as sideways, and an editor's sideways step stops at its widest line. See ScrollBox. (#​1761)
  • core: 896 symbols that terminals draw in one cell take one cell in every width method, also with VS15. Examples are ‼, ⁉, ☢, ⚛, 🖥, 🗑, and the Mahjong, domino, and playing-card tiles. Every measured terminal advances one cell for at least 892 of the 896. Before, they took two cells, so text after them moved one column right, the cursor landed past the text, and old cells stayed on screen. With VS16, as in ❤️, they take two cells in the grapheme methods and one cell in wcwidth, so under tmux and screen ❤️ is still one cell too narrow, until OpenTUI selects the width method correctly. In classic Windows conhost, which gives VS15 its own cell, 53 of these symbols followed by VS15 are now one cell off. Some terminals draw a glyph wider than the one cell that they advance, so it covers the next character, extends into a blank cell, or is cut off; this comes from the terminal, not from the layout. See Text and terminal cells. (#​1762)
  • core: detectLinks() styles bare URLs with markup.link.label from the new syntaxStyle context field and keeps their other attributes. Bare URLs in Markdown prose now look like link labels. See Markdown. (#​1765)
  • core: An audio stream whose decoder reads past maxProbeBytes rejects with the decoder error. Before, it could play a truncated prefix and end without an error. See Streaming audio. (#​1759)
  • core: The embedded terminal uses a newer Ghostty and no longer answers Kitty graphics or glyph protocol queries, and it ignores OSC 72 drag-and-drop commands. DECSTR, CSI g, DECRQM, and other controls now behave as in xterm. XTGETTCAP answers from Ghostty's terminfo, which claims some features that the embedded terminal does not draw. See Embedded terminal. (#​1714)
  • native: Highlight reads mark styled-text styles with the new OT_STYLE_RUN_FLAG bit, and user highlights reject such style ids. Styled text no longer registers chunkN styles in the attached SyntaxStyle, and ot_text_buffer_clear without reset keeps styled highlights but drops their links. The styled-text batch accepts buffers that have an attached style. See Editing buffers and views. (#​1768)
  • native: ot_editor_view_get_viewport, ot_editor_view_set_scroll_margin, and a local ot_editor_view_select can lay out text and return OT_OUT_OF_MEMORY. This occurs only while a scroll to the cursor is pending. See C. (#​1751)
  • native: ot_scene_set_paint rejects a translation that is not finite or exceeds FLT_MAX, ot_scene_set_style stores finite margin, padding, position, and gap values beyond ±1e30 as ±1e30, and OT_LAYOUT_PAINT clamps an axis outside the 32-bit cell range. See C. (#​1682)
Fixed
  • core: Text with a fractional flex size rounds like a box and stays inside the rounded box of its parent. Before, Yoga rounded the right edge of text up but rounded containers to the nearest cell, so the last glyph of each wrapped line could draw under the next column or under a ScrollBox scrollbar. Some layouts move by one cell. Text at a half-cell offset, such as centered text with an odd leftover, moves right or down to the position of a box of the same size. A box centered inside another centered box, with odd leftovers at both levels, can move left or up. Update snapshots of such layouts. The public yoga.ts API keeps the upstream rounding. See Layout. (#​1728)
  • core: A node with layout outside 32-bit cell coordinates, such as left = 2 ** 31 - 1 or margin = 1e15, no longer makes every later frame fail with InvalidArgument. Native code clamps only the geometry that it paints, clips, and hit-tests. See Layout. (#​1682)
  • core: With the wcwidth width method, drawText and encodeUnicode give a multi-code-point cluster, such as 👩‍💻, the same width as the layout. Before, text after the cluster landed two cells to the left. Clusters wider than four cells, such as 👨‍👩‍👧, are still drawn too narrow. See Text and terminal cells. (#​1773)
  • core: A ScrollBar position is never negative. Before, a ScrollBox with content that fit the viewport could report a scrollTop or scrollLeft such as -7 while its content did not move. A ScrollBar's onChange value and scrollPosition now agree, and with sticky bottom over short content, a sideways scroll no longer stops the box from following new content. See ScrollBar. (#​1769)
  • core: destroy() leaves raw mode after the Session restores the terminal, and resume() enters raw mode before mouse reporting starts again. Before, mouse reports could show as text such as ^[[<35;30;5M at the shell prompt. suspend() still leaves raw mode at once, so mouse reports can still echo there. See Lifecycle and cleanup. (#​1720)
  • core: Audio.playStream() with an MP3 whose first chunk is larger than the decoder probe limit always rejects with { action: "decoder", errorCode: -3 }. Before, it sometimes rejected with { action: "write", status: -1 }, depending on thread timing. See Streaming audio. (#​1759)
  • core: An Input with wrap off limits its horizontal scroll by the widest line, not the widest text piece, so it no longer jumps sideways on resize. A typed character lands on the same side of zero-width text as in the same text loaded in one piece. See Input. (#​1747)
  • core: Undo and redo in a Textarea scroll to the cursor in the restored text. Before, they used the layout from before the undo and could leave blank rows below the text. See Textarea. (#​1751)
  • core: Undo and redo in a Textarea or Input emit content-changed, so onContentChange and line-info-change run. Before, an app that synced on content changes kept the text from before the undo, and the line-number gutter kept its old width. See Textarea. (#​1790)
  • core: A node that is resized and then given an onLifecyclePass before its first frame gets its first onResize. Before, a buffered node kept its old buffer size and clipped its drawing. See Custom renderables. (#​1754)
  • core: Two text buffers that share a SyntaxStyle keep their own styled-text colors. Before, each update of one buffer changed the colors of the other. See Editing buffers and views. (#​1768)
  • core: In an EmbeddedTerminalRenderable, RIS (ESC c) and DECSTR (CSI ! p) reset the palette that a program set with OSC 4, and keep the OSC 10, 11, and 12 colors. Tools such as kitten icat now use their text fallback and do not show blank cells. See Embedded terminal. (#​1714)

API changes and documentation: https://opentui.com/docs/releases/0.7.0

What's Changed
New Contributors

Full Changelog: anomalyco/opentui@v0.6.8...v0.7.0

v0.6.8

Compare Source

Fixed

  • core: A left or right wheel step no longer shifts a Text or Code with wrapMode: "none" and truncate: true by one column. A text with a sideways offset now returns to column 0 when truncate becomes true or wrapMode changes to "word" or "char". (#​1745)

API changes and documentation: https://opentui.com/docs/releases/0.6.8

What's Changed

Full Changelog: anomalyco/opentui@v0.6.7...v0.6.8

v0.6.7

Compare Source

In 0.6.6, OpenTUI gave the text a\u200Ebcdef seven cells, but the terminal drew it in six. U+200E, the left-to-right mark, is one of Unicode's default-ignorable code points, and terminals draw these in no cell. OpenTUI gave each of them one cell. As a result, it placed the rest of the row one column right of where the terminal drew it. A full frame looked correct, but the next partial update did not: in Ghostty, changing the f to Z showed abcdefZ. Hebrew and Arabic text often carries direction marks, so these rows broke when they were updated. In 0.6.7, every default-ignorable code point takes no cell. Layout, cursor movement, drawing, and encodeUnicode use the same width, so they agree. Two exceptions remain. The soft hyphen U+00AD keeps one cell, because terminals draw it. The conjoining Hangul fillers U+115F and U+1160 keep their widths, because each starts a syllable cluster.

When more code points have zero width, an editor bug becomes more common. In 0.6.6, a Textarea line that started with U+200B trapped the cursor. Right did not move past it from column 0, and Delete did nothing there. No key could delete a line that held only zero-width text. Without a fix, a Hebrew line that starts with U+200F would act the same way. 0.6.7 fixes both cases. A border character must still fill exactly one cell, so a customBorderChars entry such as U+200E or U+FFA0 is now an error. Before, it gave a misaligned border. Replace it with a character that takes one cell, such as a space.

A terminal reports mouse motion only when the pointer enters a new cell. In 0.6.6, the renderer gave a left-button drag to the renderable under the first motion report. A fast drag on a one-column ScrollBox scrollbar thumb often reported its first motion over a neighbor. The bar then did not scroll, and the thumb kept its dragging state. Now the renderable that receives the left press owns the drag. If the press hit nothing, or its renderable was destroyed, the renderable under the pointer is the source, as before. A press that starts or extends a text selection still follows the selection path.

registerBaseLayoutFallback() is for keyboard layouts that cannot type the character of a binding. On a Russian layout, Ctrl+с must still run ctrl+c. In 0.6.6, the addon used the Kitty base-layout key for every chord that had no direct binding. On Dvorak, Ctrl+J then ran a ctrl+c binding, Ctrl+E ran ctrl+d, and Ctrl+; ran ctrl+z. The addon now uses the same rule as Core's key bindings. It falls back only when the typed key is one non-ASCII character, or when the base-layout key is a digit. The digit case is for AZERTY, where the digit keys type & " ' ( - _ and Ctrl+& must still match ctrl+1. Some punctuation on non-Latin layouts still does not fall back, for example Hebrew ' on the W key.

In @opentui/react, each root.render() call created a new reconciler container. It dropped the old tree and did not run its effect cleanups. A later call now updates the mounted tree, as react-dom does:

const root = createRoot(renderer)
root.render(<App theme="dark" />)
root.render(<App theme="light" />) // App keeps its state

An explicit root.render() also resets the internal error boundary. If a tree failed with an uncaught error, the next call tries it again.

OpenCode draws each diff hunk as a separate DiffRenderable. To line up the gutters, it put an invisible sign on line -1 and measured the gutters again after mount. The new gutter option sets that layout directly:

const gutter = { paddingLeft: 2, minDigits: 4 }
const first = new DiffRenderable(renderer, { diff: firstHunk, gutter })
const second = new DiffRenderable(renderer, { diff: secondHunk, gutter })

minDigits always reserves the +/- sign column. Thus both code columns start at the same cell, also when a hunk has no signs. Line -1 still works, for compatibility.

Breaking changes

  • core: A Box with a default-ignorable character in customBorderChars, such as U+200E or U+FFA0, throws InvalidArgument at creation, and drawGrid rejects such a character. Use a border character that takes one cell. See Box. (#​1675)

Added

  • core: The gutter option and property of DiffRenderable set paddingLeft, paddingRight, and minDigits for the line-number gutter. Diffs with the same minDigits align their code columns. See Diff. (#​1684)
  • core: LineNumberRenderable.getGutterLayout() and setGutterLayout() read and set the gutter padding, minimum digits, and minimum widths as a GutterLayout. See Line number gutter. (#​1684)

Changed

  • react: A later root.render() updates the mounted tree, keeps component state, and tries again after a tree that failed with an uncaught error. Effects clean up when the tree is replaced, on root.unmount(), and on renderer.destroy(). See React bindings. (#​1692)
  • keymap: registerBaseLayoutFallback() uses the base-layout key only when the typed key is one non-ASCII character or the base-layout key is a digit. Dvorak Ctrl+J now matches ctrl+j, not ctrl+c. See Built-in keymap addons. (#​1718)

Fixed

  • core: Bidirectional marks and other default-ignorable code points take no cell in layout, cursor movement, drawing, and encodeUnicode. Before, a partial update of a row with such a mark wrote one column to the right. See Text and terminal cells. (#​1675)
  • core: In a Textarea or Input, Right, Shift+Right, and Delete move over zero-width characters at the start of a line or a pasted chunk. A double or triple click at such a line start selects the same text as on a line without the prefix. See Textarea. (#​1681)
  • core: Delete, Backspace, Delete Line, and a selection delete remove text that holds only zero-width characters, such as U+200B, U+200D, or a combining mark. Delete Line also keeps a zero-width character that starts the next line. See Textarea. (#​1683)
  • core: With word wrap, a combining mark at the start of a typed or appended chunk stays on the line of its base character. Before, the mark moved to the next line and did not draw. See Text and terminal cells. (#​1715)
  • core: After a measure provider fails, it gets one more call before the next frame. Before, the node stayed 0 cells wide until invalidateIntrinsicSize(). See Custom renderables. (#​1680)
  • core: A left-button drag goes to the renderable that got the press, also when the first motion report is over a different renderable. A release over the drag source or its descendant no longer sends drop and a second up. See Interaction, focus, and selection. (#​1716)
  • core: Key bindings fall back to the base-layout key when it is a digit, so AZERTY Ctrl+& matches ctrl+1. They no longer fall back to a space base-layout key. See Keyboard input. (#​1718)
  • keymap: registerBaseLayoutFallback() no longer throws or reports an event-match-resolver-error when the base-layout key is a space. See Built-in keymap addons. (#​1718)
  • core: On Node, a -0 integer argument goes to native code as 0. Before, calls such as EditBuffer.setCursor, pushScissorRect, and CliRenderer.hitTest threw TypeError: Argument N must be an int32. See Runtime and platform support. (#​1709)

API changes and documentation: https://opentui.com/docs/releases/0.6.7

What's Changed

Full Changelog: anomalyco/opentui@v0.6.6...v0.6.7

v0.6.6

Compare Source

Previously, most layout setters ignored null, undefined, or both, so the node kept its old style. As a result, a React or Solid component that removed a style prop kept the old layout. A plugin that read Unit.Undefined from a getter also could not write that value back. In 0.6.6, a nullish write restores the default that the constructor uses when the option is omitted:

const box = new BoxRenderable(renderer, { paddingX: 2 })
box.paddingLeft = 5 // pads the left side by 5
box.paddingLeft = undefined // pads it by 2 again, from paddingX

Margin, padding, gap, min and max sizes, and flexBasis become unset, so an edge falls back to its shorthand again. top, right, bottom, and left also unset the native edge. Before, the getter reported undefined while layout kept the old offset. width and height become "auto", position becomes "relative", overflow becomes "visible", and visible becomes true. Invalid values are still ignored. Not every default comes back. Class defaults such as the row direction of ScrollBoxRenderable stay as they are, and flexShrink becomes 1.

If a custom renderable passes its own width or height to super(), it should override the new protected defaultDimension(kind) method. A nullish write then restores that size instead of "auto". Built-in classes that size themselves, such as InputRenderable, TabSelectRenderable, and ASCIIFontRenderable, already override it.

Added

  • core: A custom renderable can override protected defaultDimension(kind) to set the width or height that a nullish write restores. See Custom renderables. (#​1678)

Changed

  • core: Assigning null or undefined to a layout property restores the default for an omitted option, and removing a React or Solid style prop does the same. See Layout. (#​1678)
  • core: Assigning null or undefined to visible shows the node. Before, null hid the node, so assign false to hide it. (#​1678)

Fixed

  • core: fillRect, drawBox, setCell, drawChar, and other calls with u32 fields now say "must be a non-negative integer" for NaN, Infinity, fractional, and negative values. Before, the error said that the value exceeded the u32 limit. See Buffer API. (#​1674)
  • core: A box title or bottomTitle over 64 KiB throws the same RangeError as drawText instead of a NativeError with the InvalidArgument status. See Box. (#​1674)
  • core: Nesting deeper than 256 levels, counting the root, throws a NativeError with the message "Scene nesting depth exceeds the native limit". Before, the message gave only the ObjectLimit status. See Layout. (#​1674)

API changes and documentation: https://opentui.com/docs/releases/0.6.6

What's Changed

Full Changelog: anomalyco/opentui@v0.6.5...v0.6.6

v0.6.5

Compare Source

OpenTUI 0.6 removed Renderable.getLayoutNode(). Before 0.6, plugins used it to read the layout styles of host renderables that they did not create. Some plugins saved a container's getMaxWidth() and getAlignSelf() so they could restore the container after they restyled it. Others checked the host layout with getFlexDirection(), or sized a panel inside a padded sidebar with getPadding(). 0.6 kept only getWidth() and getMargin(edge), so this code had nothing to call.

In 0.6.5, renderables have the other style getters of a Yoga Node, with the same names and return types. To move, remove the getLayoutNode() call:

// Before 0.6
const saved = host.getLayoutNode().getMaxWidth()

// 0.6.5
const saved = host.getMaxWidth()

These getters return the declared style, not the computed layout. A write shows up at once, before the next layout pass. After box.maxWidth = 40, box.getMaxWidth() returns { unit: Unit.Point, value: 40 }, but box.width still holds the computed width from the last pass. After destroy(), the getters do not throw. They return Yoga's unset values, as getWidth() does.

An edge getter reads only the value set for that exact edge, so this case needs care. The padding option sets Edge.All, paddingX sets Edge.Horizontal, and paddingLeft sets Edge.Left. After padding: 2, getPadding(Edge.Left) returns a Unit.Undefined value, but layout still pads the left side by 2. That is because Yoga falls back from Edge.Left to Edge.Horizontal and then to Edge.All. To get the declared left padding, check the edges in that same order:

import { Edge, Unit } from "@opentui/core/yoga"

const left = [Edge.Left, Edge.Horizontal, Edge.All]
  .map((edge) => sidebar.getPadding(edge))
  .find((value) => value.unit !== Unit.Undefined)

Renderables still have no getter for computed padding, margin, or border. For padding declared in points, the declared value and the computed value are the same.

Added

  • core: Renderables have the remaining Yoga Node style getters, such as getHeight(), getMaxWidth(), getPadding(edge), getGap(gutter), getFlexDirection(), and getAlignSelf(). Use them instead of style reads through getLayoutNode(). See Layout. (#​1667)

API changes and documentation: https://opentui.com/docs/releases/0.6.5

What's Changed

Full Changelog: anomalyco/opentui@v0.6.4...v0.6.5

v0.6.4

Compare Source

Fixed

  • core: Kitty images in iTerm2 no longer go black or blank after a move, a scroll, or a layout change. The renderer now deletes each old placement by its placement id, because iTerm2 also frees the image data when it gets an image-wide delete. (#​1662)

API changes and documentation: https://opentui.com/docs/releases/0.6.4

What's Changed

Full Changelog: anomalyco/opentui@v0.6.3...v0.6.4

v0.6.3

Compare Source

A coding agent such as opencode can wait for minutes at a permission prompt in a
background tab. Before this release, the terminal could not tell the user about it.
In 0.6.3, the agent can report its state to the terminal:

renderer.setProgramStatus({ state: "blocked", kind: "permission", app: "opencode", message: "Allow bash?" })

This call writes one report in the
Program Status Protocol,
OSC 7501. The terminal decides how to show the report. It can use a tab
indicator, an inbox entry, or a desktop notification. A terminal that does not
implement the protocol ignores the sequence, so setProgramStatus() does not
wait for capability detection. capabilities.program_status only tells you that
a reply arrived. A false value can also mean that the reply has not arrived
yet.

Each report replaces one record, and a field that a report omits is gone from
the record. Send app and title with every report that needs them. A program
that runs several tasks can give each task its own record with an id. It can
then remove that record with clear:

renderer.setProgramStatus({ state: "working", id: "subagent-1", title: "Explore", progress: 40 })
renderer.setProgramStatus({ state: "clear", id: "subagent-1" })

Records belong to the terminal, not to the renderer. OpenTUI does not clear them
when the renderer is destroyed or suspended. When the process exits, the
terminal drops working and blocked records. It keeps done and error
records for the user to find. If the user should see the result, report done or
error before the program exits. OpenTUI also does not wrap reports in tmux or
GNU Screen passthrough. The outer terminal cannot tell panes apart or see the
program exit, so a working record from a pane would never clear.

C hosts send the same report with ot_session_control() and
OT_CONTROL_PROGRAM_STATUS. The payload is an ot_session_program_status struct,
followed by the id, app, title, and message bytes. Native code checks every
report on both paths before it writes anything. A report that a terminal would
discard or only partly read fails at the call.

Added

  • core: renderer.setProgramStatus(status) and
    NativeSession.setProgramStatus(status) send one OSC 7501 program status
    report. They return false when OpenTUI does not queue the report, and they
    throw for an invalid report. See
    Program status.
    (#​1657)
  • core: TerminalCapabilities.program_status is true when the terminal
    replies to the OSC 7501 ; ? query. The renderer sends this query at startup
    with the other capability queries, but not to foot. See
    Terminal capabilities.
    (#​1657)
  • **native

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch from d94b49b to 5718e8e Compare August 24, 2026 19:06
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/core to v0.5.7 fix(deps): update dependency @opentui/core to v0.5.8 Aug 24, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 3 times, most recently from 3787cdd to 658be89 Compare August 27, 2026 06:17
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/core to v0.5.8 fix(deps): update dependency @opentui/core to v0.5.9 Aug 28, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 12 times, most recently from 3f9cf28 to b75c4b8 Compare August 31, 2026 05:12
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/core to v0.5.9 fix(deps): update dependency @opentui/core to v0.5.10 Sep 1, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 7 times, most recently from 9171811 to 01bcfd1 Compare September 6, 2026 06:06
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/core to v0.5.10 fix(deps): update dependency @opentui/core to v0.5.11 Sep 7, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 3 times, most recently from f1e9d16 to 58804d7 Compare September 8, 2026 02:01
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 16 times, most recently from d80a780 to 24295eb Compare September 21, 2026 17:03
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 2 times, most recently from f4a813d to e075063 Compare September 22, 2026 22:43
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/core to v0.5.11 fix(deps): update dependency @opentui/core to v0.5.12 Sep 22, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-core-0.x branch 10 times, most recently from 70d54ba to 0354525 Compare September 29, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants