Skip to content

fix(deps): update dependency @opentui/keymap to v0.8.0 - #466

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

renovate[bot] wants to merge 1 commit into
masterfrom
renovate/opentui-keymap-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/keymap (source) 0.5.10 → 0.8.0 age confidence

Release Notes

anomalyco/opentui (@​opentui/keymap)

v0.8.0

Compare Source

Most of 0.8.0 makes each frame cheaper. In 0.7.0, every frame cleared and repainted the whole screen and compared every row with the terminal, even when only a spinner had moved. Now an unchanged frame paints nothing, and a frame with a small change repaints only the rows around it. In OpenCode, a frame with a spinner or a streamed line does about a twentieth of the cell operations it did.

ScrollBox now scrolls and follows new content in native code. Animations share one clock: before, an app with several animations could draw twice as many frames as the animations needed.

The native input parser is still opt-in. It now keeps a mouse wheel report that a slow link splits, which the default parser can type into the focused input as text. To try it, set OTUI_NATIVE_INPUT_PARSER=1, or pass experimental_nativeInput: true to the renderer.

Wide emoji sequences, such as a family emoji in tmux, draw at their full width, and long wrapped or highlighted lines draw their last rows as fast as their first.

In 0.7.0, a ScrollBox was twelve scene nodes. It kept its scroll position in four copies, followed its sticky edge through a six-flag state machine, and learned its range from two separate resize hooks. Because the viewport size and the content size came from different hooks, a frame in which the box and its content grew together reset the position to 0. Vertical viewport padding left the last rows out of reach, and the scrollbar thumb was half a cell until the first terminal resize. In 0.8.0, a ScrollBox is one box with a native scroll control. After each layout solve, native code computes the range, follows the sticky edge, clamps the offset, and shows or hides the bars, all from one layout. Following new content no longer needs a round trip to JavaScript.

That change has a cost for code that reached into the parts. wrapper, viewport, content, and the two scrollbars are now views of the native state, not renderables. They still have geometry, colors, padding, and their events, but they have no children of their own. Children of a ScrollBox report the ScrollBox as their parent, and a press on a scrollbar targets the ScrollBox. Code that compared a node with the content part now compares it with the ScrollBox itself:

// 0.7.0
if (node === scroll.content) return
// 0.8.0
if (node === scroll) return

With the scroll state in native code, a scroll can also wait for layout. Before, an app that loaded older messages above the reader and then corrected the position had to wait two animation frames, and the reader saw one frame at the old position. Now scrollTo and scrollBy apply again after the next frame's layout, and anchor holds a row in place while the content above it changes. Release the anchor in afterLayout, so that the frame that lays out the new rows still holds the place:

const release = scrollbox.anchor(firstVisibleRow)
await loadOlderMessages()
scrollbox.afterLayout(release)

In an OpenCode transcript, this took the shifted frames during a backfill from 1 to 0, and the in-between frames over 6 message jumps from 6 to 0.

An animation slower than the renderer, such as a spinner, used to have two options. It could set live: true, which runs the loop at targetFps and keeps renderer.idle() from resolving. Or it could run its own timer that calls requestRender(). With timers, every component gets its own frames, so three components at 20, 12.5, and 10 fps drew 40 frames per second. renderer.onAnimationTick puts every subscriber on one clock. Due times are multiples of 1000 / fps, so equal rates tick together. Each frame, whatever its cause, also runs every tick that is due within its tolerance. The same three components now draw 20 frames per second, and OpenCode during an agent turn draws 26 instead of 52 to 55:

const frames = ["⠋", "⠙", "⠹", "⠸"]
let i = 0
const stop = renderer.onAnimationTick(12.5, () => {
  spinner.content = frames[i++ % frames.length]
})

A renderable can set animationFps instead. React components use useAnimationTick, and Solid components use onAnimationTick.

Before 0.8.0, OpenTUI wrapped every frame in synchronized output (CSI ? 2026 h … CSI ? 2026 l), even when the terminal never reported mode 2026. Over a slow link, those wrappers cost bytes on every frame. A tmux between the app and the link redraws each line that an update touched when the update ends, so a streaming scene sent 64 KB/s instead of 41 KB/s. On a 48 KB/s SSH link, that is a lag of 2.0 s instead of 75 ms. The new default, synchronizedOutput: "auto", removes the wrapper in a remote session once the startup replies arrive, but only when the terminal did not report mode 2026, or when the app writes to process.stdout inside tmux, screen, or Zellij on its own host. In the multiplexer case, OpenTUI gives up whole frames to save bytes, so tmux can show partial frames. If you want whole frames on a slow link, set synchronizedOutput: "always".

Breaking changes
  • core: The wrapper, viewport, content, verticalScrollBar, and horizontalScrollBar of a ScrollBox are facade objects (ScrollBoxWrapper, ScrollBoxViewport, ScrollBoxContent, ScrollBoxBar), not renderables. Children report the ScrollBox as their parent, so compare a node with the ScrollBox, not with scroll.content. See ScrollBox. (#​1805)
  • core: ScrollBox scrollbars have no slider, startArrow, or endArrow, overflow is always "hidden", getChildrenCount() counts the rows, and primaryAxis follows the content direction. ScrollBox ignores renderable options on its parts outside each part's supported color, layout, padding, bar, and callback fields, and viewport padding that is not whole cells throws. See ScrollBox. (#​1805)
  • core: RenderContext requires the clock and onAnimationTick fields, so a custom RenderContext must implement them. The renderer and the scrollback snapshot context supply both. See Custom renderables. (#​1814, #​1803)
  • core: The protected Renderable._hasFocusedDescendant field is removed, so a subclass that reads it gets undefined. Read hasFocusedDescendant instead, and use FFIRenderLib.contextDrawSceneBody() instead of contextDrawSceneText(). See Custom renderables. (#​1778)
  • native: OT_SCENE_RECORD_SCENE_TEXT is now OT_SCENE_RECORD_SCENE_BODY, and ot_buffer_draw_scene_text is now ot_buffer_draw_scene_body, with the same value and layout. The reserved field of ot_scene_stats is now damage_rows. See C. (#​1778, #​1783)
  • native: In the Zig module, a CliRenderer exists only as the renderer of a Session, and Context.drawSceneText and drawSceneTextOn are now drawSceneBody and drawSceneBodyOn. BufferedOutput and the standalone create, render, and setup calls are removed, so render through a Session. See Zig. (#​1806, #​1778)
Added
  • core: renderer.onAnimationTick(fps, callback, { tolerance }) runs callbacks on one shared animation clock, so animations at different rates share frames. Renderable.animationFps ticks the protected onAnimationTick() of a node while it is drawn, and a Timeline with a frameRate below targetFps ticks on the clock. See Animation and Timeline. (#​1803)
  • react: useAnimationTick(fps, callback, options?) subscribes a component to the shared animation clock and commits state updates from the callback in the tick's frame. An undefined rate pauses it. See React bindings. (#​1803)
  • solid: onAnimationTick(fps, callback, options?) subscribes a component to the shared animation clock, and fps can be an Accessor<number | undefined> that returns undefined to pause it. See Solid bindings. (#​1803)
  • core: ScrollBoxRenderable.anchor(node) keeps a row at its place in the viewport through layout changes, and afterLayout(callback) runs once after the next frame's layout and before it paints. scrollChildIntoView() accepts a node, an align of "nearest", "start", or "end", and an inset. See ScrollBox. (#​1811)
  • core: ScrollBoxRenderable.scrollX and scrollY read and update which axes scroll after construction. See ScrollBox. (#​1805)
  • core: The test renderer option nativeSceneVerify, on by default, compares each frame that retained paint skips or repaints in part with a full repaint, and it throws on a mismatch. See Testing. (#​1783)
  • core: Rex gets OSC 8 hyperlinks when its XTVERSION reply or TERM names rex or xterm-rex. See Terminal capabilities. (#​1819)
  • native: A box can be a native scroll node. ot_scene_set_scroll, ot_scene_scroll_to, ot_scene_get_scroll, ot_scene_scroll_request, and ot_scene_set_scroll_arrow configure, scroll, and read it, and OT_SCENE_HOOK_SCROLL asks for an OT_SCENE_FRAME_SCROLL request when its state changes. See Frames and output. (#​1798, #​1811)
  • native: ot_scene_stats.damage_rows counts the rows that a frame diffs, and the new OT_SCENE_FULL_PATH_* bits give the reason for a full repaint. OT_SCENE_DEBUG_VERIFY compares retained frames with a full repaint. See Frames and output. (#​1783, #​1808)
  • native: OT_EDIT_POSITION_END and OT_EDIT_POSITION_PREV_CHAR, also NativeEditPositionQuery.End and PrevChar in Core, read the end of the text and the position that Left moves to. They do not move the cursor. See C. (#​1848)
  • native: OT_INPUT_EXPECT_SENTINEL holds split terminal replies while the host waits for a DA1 reply. OT_TERMINAL_REPLY_SENTINEL, OT_TERMINAL_SYNC_ALWAYS, OT_TERMINAL_SYNC_NEVER, and OT_TERMINAL_HOST_MULTIPLEXER set the reply sentinel and the synchronized-output policy of a Session. See Host I/O and time. (#​1793, #​1816, #​1851)
Changed
  • core: The new synchronizedOutput option defaults to "auto", which removes the synchronized-output wrapper in some remote sessions after the startup replies arrive. Set "always" for the old behavior, or "never" to never wrap a frame. See Renderer. (#​1851)
  • core: The renderer applies capability replies whenever they arrive, and it emits capabilities and repaints only when the capabilities change. After startup, handlers added with addInputHandler no longer see these replies, and the theme mode follows each OSC 11 reply. See Terminal capabilities. (#​1807)
  • core: With the native input parser, the renderer ends each batch of terminal queries with DA1 and holds split replies until the DA1 reply comes, so the focused input no longer gets reply text over a slow link. The startup window closes after one round trip instead of 5 s, and with both parsers a pixel-size query without a reply ends after 5 s. See Terminal capabilities. (#​1816, #​1793)
  • core: An unchanged scene frame paints nothing, and a frame with a small change clears and repaints only bands around its damaged rows, also when paint hooks draw it. An OpenCode frame at 160×48 does about 1,000 to 1,600 cell operations instead of about 28,000. See Rendering pipeline. (#​1783, #​1808, #​1810, #​1804, #​1839, #​1840)
  • core: Scenes with hooks or a ScrollBox keep their preparation across frames, and feedback visits only the nodes that can send a request or that changed. The order of update calls between nodes whose replies race is not specified. See Rendering pipeline. (#​1802, #​1809)
  • core: drawFrameBuffer() copies a source without respectAlpha at full opacity, also when the screen or the source holds a grapheme or a link, so transparent source cells show as holes. A transparent box border of space glyphs keeps the glyph below it in every case. See Buffer API. (#​1810)
  • core: Siblings with the same zIndex paint in attachment order. A child whose zIndex changes and then goes back to its old value gets its original paint position back. See Renderables. (#​1812)
  • core: A ScrollBox scrolls and follows new content in native code, so an append to a 1,000-row sticky ScrollBox sends no request to the host. A held scrollbar arrow repeats on the renderer's clock, so a custom Clock drives it. See ScrollBox. (#​1805, #​1814)
  • core: scrollTo() and scrollBy() scroll at once and again after the next frame's layout, so a scroll made in the same tick as a content change uses the new layout. scrollTo(scrollHeight) and scrollTo(Infinity) stay at the end after the content grows. See ScrollBox. (#​1811)
  • core: Each row of a long wrapped line, or of a long highlighted line, finds its first cluster and its style span by binary search. The bottom 30 rows of a 131,072-column highlighted line take 480 steps instead of 1.9 million. See Code. (#​1781, #​1796)
  • core: Release builds allocate native memory through libc, which reuses freed memory. A Context with 200 streamed text buffers (7.7 MB live) holds about 11 MB instead of 105 MB. See Resources and ownership. (#​1795)
  • solid: @opentui/solid and @opentui/keymap accept solid-js ^1.9.12 as a peer dependency instead of 1.9.12 alone. See Solid bindings. (#​1786)
Fixed
  • core: A ScrollBox keeps its position when it and its content grow in the same frame, and vertical viewport padding no longer hides the last rows. The thumb is proportional to the viewport from the first frame, and scrollbar onChange options run. See ScrollBox. (#​1805, #​1798)
  • core: A host clipboard that read once no longer keeps an idle Wayland connection that collects unread offers. On Hyprland, those offers froze the desktop for up to 10 s or more when the process exited or the next paste read them. See Clipboard. (#​1825)
  • core: The host clipboard tries Wayland again after an unsupported connection, and on WSL it also tries /mnt/wslg/runtime-dir/$WAYLAND_DISPLAY. On WSLg, a read that loses focus to another window maps a new helper surface, at most two times, instead of failing. See Clipboard. (#​1845, #​1849)
  • core: The Wayland clipboard works with libwayland-client 1.10 and later, not only 1.20 and later. On Ubuntu 20.04 and Debian 11, image paste no longer falls back to X11 without a message. See Clipboard. (#​1846)
  • core: After the X server or XWayland restarts, the X11 clipboard connects again, so operations no longer fail until the app restarts. On WSL, a zero-byte X11 result ends the read as empty. See Clipboard. (#​1844)
  • core: On Windows and macOS, a running clipboard operation settles at its deadline or on cancel. Before, a read with timeoutMs: 1000 that an unresponsive owner blocked took 17.3 s. See Clipboard. (#​1847)
  • core: A change to syntaxStyle or fg of a MarkdownRenderable restyles list markers, and with internalBlockMode: "top-level" it also restyles list items and table cells. Before, they kept the old theme's colors. See Markdown. (#​1841)
  • core: When a clip, the buffer edge, or a text view's edge cuts a wide glyph, each visible cell draws as a space in the glyph's style. Before, the cell showed what was behind the text, and at a view's left edge a cut wide glyph or tab shifted the rest of the line left. See Text and terminal cells. (#​1838)
  • core: Under wcwidth, a cluster can span up to 16 cells, so ZWJ families of 6 or 8 cells no longer leave blanks or stale cells. A wider cluster draws as blank cells of its width, and OSC 66 explicit width is capped at 7 cells and padded with spaces. See Text and terminal cells. (#​1779)
  • core: Streamed or edited text measures, wraps, draws, and deletes like the same text loaded in one piece, also when an append or edit splits a grapheme. After such an edit, the cursor goes after the joined grapheme and extmarks shift by the change in width. See Editing buffers and views. (#​1782, #​1848)
  • core: A box title is placed against the box's own edges, so a box at a negative x or past the right edge of the screen shows its title in the correct place. See Box. (#​1797)
  • core: TimeToFirstDrawRenderable and LineNumberRenderable at a fractional position no longer fail the frame, and both draw correctly with buffered: true. See TimeToFirstDraw and Line number gutter. (#​1826)
  • core: A subclass that calls super.renderSelf() on an ArrowRenderable at a fractional position no longer fails the frame, super.renderSelf() on a SliderRenderable matches native thumbs at negative fractional positions, and a buffered BoxRenderable, SliderRenderable, or ArrowRenderable draws at its own origin instead of shifted. See Custom renderables. (#​1799, #​1778)
  • core: With the native input parser, a UTF-8 character that a pause splits waits for its other bytes, and so does a mouse report cut after ESC [. Before, ✓ arrived as Alt keys and a wheel notch typed <64;87;26M. 8-bit Meta input now reads as UTF-8. See Keyboard input. (#​1791, #​1794)
  • solid: Conditional content that switches off and back on no longer leaves an empty text node in its place. See Solid bindings. (#​1786)
Security
  • solid: @opentui/solid uses babel-preset-solid 1.9.16. Solid releases before 1.9.16 depend on Seroval versions with a critical advisory (GHSA-p6vx-979v-rg4c), so install solid-js 1.9.16 or later. See Solid bindings. (#​1786)

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

What's Changed
New Contributors

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

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 su

[!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 is behind base branch, 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 changed the title fix(deps): update dependency @opentui/keymap to v0.5.7 fix(deps): update dependency @opentui/keymap to v0.5.8 Aug 24, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 5 times, most recently from 0b1d388 to 69f0606 Compare August 28, 2026 01:47
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/keymap to v0.5.8 fix(deps): update dependency @opentui/keymap to v0.5.9 Aug 28, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 12 times, most recently from da41dcc to 31bf5b9 Compare September 1, 2026 19:49
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/keymap to v0.5.9 fix(deps): update dependency @opentui/keymap to v0.5.10 Sep 1, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 7 times, most recently from a79e085 to a18a77b Compare September 7, 2026 18:29
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/keymap to v0.5.10 fix(deps): update dependency @opentui/keymap to v0.5.11 Sep 7, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 2 times, most recently from 67f552d to 6b2fb0a Compare September 8, 2026 02:01
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 16 times, most recently from 52cf01f to 95e4b52 Compare September 21, 2026 17:03
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 2 times, most recently from 9e9c026 to df6e06d Compare September 22, 2026 22:43
@renovate renovate Bot changed the title fix(deps): update dependency @opentui/keymap to v0.5.11 fix(deps): update dependency @opentui/keymap to v0.5.12 Sep 22, 2026
@renovate
renovate Bot force-pushed the renovate/opentui-keymap-0.x branch 10 times, most recently from debda6f to d6882d7 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