@opentui/core
0.6.6 · 14 Oct 2026MIT677.5k / weekRepositoryHomepagenpmAtom feed
Latest releases
All 160 releases- 0.6.x · latest line
0.6.6
14 Oct 2026core: A custom renderable can override
protected defaultDimension(kind)to set thewidthorheightthat a nullish write restores. See Custom renderables.core: Assigning
nullorundefinedto a layout property restores the default for an omitted option, and removing a React or Solid style prop does the same. See Layout.core: Assigning
nullorundefinedtovisibleshows the node. Before,nullhid the node, so assignfalseto hide it.
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 readUnit.Undefinedfrom 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 paddingXMargin, padding, gap, min and max sizes, and
flexBasisbecome unset, so an edge falls back to its shorthand again.top,right,bottom, andleftalso unset the native edge. Before, the getter reportedundefinedwhile layout kept the old offset.widthandheightbecome"auto",positionbecomes"relative",overflowbecomes"visible", andvisiblebecomestrue. Invalid values are still ignored. Not every default comes back. Class defaults such as the row direction ofScrollBoxRenderablestay as they are, andflexShrinkbecomes1.If a custom renderable passes its own
widthorheighttosuper(), it should override the newprotected defaultDimension(kind)method. A nullish write then restores that size instead of"auto". Built-in classes that size themselves, such asInputRenderable,TabSelectRenderable, andASCIIFontRenderable, already override it.Added
core: A custom renderable can override
protected defaultDimension(kind)to set thewidthorheightthat a nullish write restores. See Custom renderables.
Changed
core: Assigning
nullorundefinedto a layout property restores the default for an omitted option, and removing a React or Solid style prop does the same. See Layout.core: Assigning
nullorundefinedtovisibleshows the node. Before,nullhid the node, so assignfalseto hide it.
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.core: A box
titleorbottomTitleover 64 KiB throws the sameRangeErrorasdrawTextinstead of aNativeErrorwith theInvalidArgumentstatus. See Box.core: Nesting deeper than 256 levels, counting the root, throws a
NativeErrorwith the message "Scene nesting depth exceeds the native limit". Before, the message gave only theObjectLimitstatus. See Layout.
API changes and documentation: opentui.com/docs/releases/0.6.6
What's Changed
core: name the real cause in three boundary errors (u32 fields, box titles, nesting depth)
core: restore layout defaults on nullish writes
0.6.5
14 Oct 2026core: Renderables have the remaining Yoga
Nodestyle getters, such asgetHeight(),getMaxWidth(),getPadding(edge),getGap(gutter),getFlexDirection(), andgetAlignSelf(). Use them instead of style reads throughgetLayoutNode(). See Layout.core: read declared layout styles on renderables
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'sgetMaxWidth()andgetAlignSelf()so they could restore the container after they restyled it. Others checked the host layout withgetFlexDirection(), or sized a panel inside a padded sidebar withgetPadding(). 0.6 kept onlygetWidth()andgetMargin(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 thegetLayoutNode()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 }, butbox.widthstill holds the computed width from the last pass. Afterdestroy(), the getters do not throw. They return Yoga's unset values, asgetWidth()does.An edge getter reads only the value set for that exact edge, so this case needs care. The
paddingoption setsEdge.All,paddingXsetsEdge.Horizontal, andpaddingLeftsetsEdge.Left. Afterpadding: 2,getPadding(Edge.Left)returns aUnit.Undefinedvalue, but layout still pads the left side by 2. That is because Yoga falls back fromEdge.LefttoEdge.Horizontaland then toEdge.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
Nodestyle getters, such asgetHeight(),getMaxWidth(),getPadding(edge),getGap(gutter),getFlexDirection(), andgetAlignSelf(). Use them instead of style reads throughgetLayoutNode(). See Layout.
API changes and documentation: opentui.com/docs/releases/0.6.5
What's Changed
core: read declared layout styles on renderables
0.6.4
13 Oct 2026core: 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.
native: delete Kitty placements by id so iTerm2 keeps the image
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.
API changes and documentation: opentui.com/docs/releases/0.6.4
What's Changed
native: delete Kitty placements by id so iTerm2 keeps the image
0.6.3
13 Oct 2026core:
renderer.setProgramStatus(status)andNativeSession.setProgramStatus(status)send one OSC 7501 program status
report. They returnfalsewhen OpenTUI does not queue the report, and they
throw for an invalid report. See
Program status.core:
TerminalCapabilities.program_statusistruewhen the terminal
replies to theOSC 7501 ; ?query. The renderer sends this query at startup
with the other capability queries, but not to foot. See
Terminal capabilities.native:
ot_session_control()withOT_CONTROL_PROGRAM_STATUSand anot_session_program_statuspayload sends a program status report.OT_CAP_PROGRAM_STATUSis set when the terminal replies to the query. See
Program status.
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, sosetProgramStatus()does not
wait for capability detection.capabilities.program_statusonly tells you that
a reply arrived. Afalsevalue 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. Sendappandtitlewith every report that needs them. A program
that runs several tasks can give each task its own record with anid. It can
then remove that record withclear: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 dropsworkingandblockedrecords. It keepsdoneanderror
records for the user to find. If the user should see the result, reportdoneorerrorbefore 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 aworkingrecord from a pane would never clear.C hosts send the same report with
ot_session_control()andOT_CONTROL_PROGRAM_STATUS. The payload is anot_session_program_statusstruct,
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)andNativeSession.setProgramStatus(status)send one OSC 7501 program status
report. They returnfalsewhen OpenTUI does not queue the report, and they
throw for an invalid report. See
Program status.core:
TerminalCapabilities.program_statusistruewhen the terminal
replies to theOSC 7501 ; ?query. The renderer sends this query at startup
with the other capability queries, but not to foot. See
Terminal capabilities.native:
ot_session_control()withOT_CONTROL_PROGRAM_STATUSand anot_session_program_statuspayload sends a program status report.OT_CAP_PROGRAM_STATUSis set when the terminal replies to the query. See
Program status.
Changed
native:
ot_session_control()now checks the payload before the terminal
phase. While the terminal is not active, an invalid payload returnsOT_INVALID_ARGUMENTinstead ofOT_INVALID_PHASE. A title with control
characters is one example. See Frames and output.
API changes and documentation: opentui.com/docs/releases/0.6.3
What's Changed
core: report program status with OSC 7501
0.6.2
12 Oct 2026core:
measureEditText(text, { widthMethod, tabWidth })returns the
editor offset at the end oftextwithout anEditBuffer.tabWidth
defaults to the tab width of a new edit buffer. See
Editing buffers and views.native:
ot_edit_measure_text()gives the same measurement through the C
interface. Atab_widthover 255 fails. See
Editing buffers and views.native: A bold, dim, italic, underlined, blinking, inverse, hidden, or
struck-through cell in the last column of a row no longer gives its
attributes to the next cells that the renderer draws. A row that ends in such
a cell writes 4 more bytes. See Frames and output.
In 0.6.1, editors started to show control characters as notation. As a result,
an editor offset is no longer the same as a JavaScript string width. The string"\f"has a width of 0, but the editor shows it as^Lin two cells.EditBuffer.measureText()gives the correct offset, but only for a buffer you
already have. To get the offset of text that was not in an editor yet, code had
to create anEditBufferonly to measure that text.In 0.6.2,
measureEditText()measures text without a buffer. It returns the
same value asmeasureText()on an edit buffer with the same width method and
tab width. Pass the settings of the editor that will hold the text.Textarea
andInputuse the renderer's width method and the default tab width, which is
2:import { createCliRenderer, measureEditText } from "@opentui/core" const renderer = await createCliRenderer() measureEditText("\f@file", { widthMethod: renderer.widthMethod }) // 7: ^L takes two cells measureEditText("a\tb", { widthMethod: renderer.widthMethod, tabWidth: 4 }) // 6C programs get the same measurement from
ot_edit_measure_text(). It takes a
context, but no edit buffer handle.Added
core:
measureEditText(text, { widthMethod, tabWidth })returns the
editor offset at the end oftextwithout anEditBuffer.tabWidth
defaults to the tab width of a new edit buffer. See
Editing buffers and views.native:
ot_edit_measure_text()gives the same measurement through the C
interface. Atab_widthover 255 fails. See
Editing buffers and views.
Fixed
native: A bold, dim, italic, underlined, blinking, inverse, hidden, or
struck-through cell in the last column of a row no longer gives its
attributes to the next cells that the renderer draws. A row that ends in such
a cell writes 4 more bytes. See Frames and output.native: If you type an emoji with a skin-tone modifier, a ZWJ sequence,
Hangul jamo, or U+FE0F in an editor, the editor now stores its correct width.
Before, 👍🏽 took 4 cells instead of 2, so the cursor, wrapping, selection, and
Right key were wrong. See Textarea.native: A combining mark that starts a new text chunk now draws on the
character before it, for example U+0301 typed afterabcin an editor or
added withTextBuffer.append. The renderer drops a mark that would make a
1-cell character 2 cells wide, such as U+FE0F. See
Text and terminal cells.native: An embedded terminal redraws all rows when the child changes the
default foreground or background color (OSC 10, 11, 110, or 111) or sets
reverse video on or off (CSI ? 5 horCSI ? 5 l). Before, rows on the
screen kept the old colors. See
Embedded terminal.core: If the host terminal uses the Kitty keyboard protocol, an embedded
terminal now sends keypad Enter, keypad navigation, lock, Print Screen, Pause,
and left and right modifier keys to the child. Keypad digits reach a Kitty
child as keypad keys, and Ctrl+keypad 0 no longer arrives as a bare0. See
Embedded terminal.core:
parseKeypress()no longer changes the caller'sBufferwhen it
decodes a single byte above 127 as a meta key. The input parser of the
renderer did not have this problem. See
Keyboard input.
API changes and documentation: opentui.com/docs/releases/0.6.2
What's Changed
bench: time only drawBox in the box benchmark
native: draw a combining mark that starts a chunk
native: redraw embedded terminal when default colors change
core: encode embedded terminal Kitty keypad, lock, and modifier keys
native: measure merged editor chunks instead of adding their widths
core: keep parseKeypress from changing the caller's Buffer
native: close an attributed run at the end of its row
Dependency tracking · free for 3 projects
Know when your own dependencies move
@opentui/core published 131 releases in the last 12 months. Inlinr folds the routine ones into a line and keeps the few that need you.
Advisories reach you
An email when a critical or high advisory affects a project, before the next install.
Upgrades checked first
Peer ranges, Node, ESM-only and types changes read before you bump.
Only what matters
Majors and breaking notes stay; patches and sibling bumps fold into one line.
Registry facts last checked 22 Oct 2026. Releases and advisories last read 22 Oct 2026.