OpenTUI 0.5.0

npmGitHub release

This release adds image rendering to the renderer.

Added

  • core: ImageRenderable shows PNG, JPEG, WebP, first-frame GIF, and raw RGBA images. It loads from a path, URL, Blob, Response, or bytes with source. The fit option accepts "fit", "cover", or "fill". The protocol option accepts "auto", "kitty", "sixel", or "blocks". "auto" picks the protocol from the terminal capabilities. See Image. (#1283)
  • core: NativeImage decodes and edits RGBA8 images. It provides NativeImage.decode(), NativeImage.load(), NativeImage.fromRgba(), resize(), extract(), extend(), rotate(), flip(), flop(), composite(), raw(), takeRaw(), and copyTo(). imageInfo() reads metadata without a full decode. OptimizedBuffer.drawImage() draws an image into a buffer. Failures throw ImageError or ImageLoadError. See NativeImage. (#1283)
  • react: The <image> element renders an ImageRenderable. It is typed by ImageProps. See React bindings. (#1283)
  • solid: The <image> element renders an ImageRenderable. It is typed by ImageProps. See Solid bindings. (#1283)
  • core: Audio input capture. Audio.openCapture() returns an AudioCaptureStream with a readable stream of Float32Array chunks. Audio.recordToFile() returns an AudioRecorder that writes a WAV file. Audio.listCaptureDevices(), Audio.selectCaptureDevice(), and Audio.clearCaptureDeviceSelection() manage input devices. Audio.startCapture(), Audio.readCaptureFrames(), Audio.getCaptureStats(), and Audio.stopCapture() give low-level access. See Audio. (#1297)
  • core: CliRenderEvents.RENDER_ERROR fires with { error, renderable } when the frame loop throws. renderable is the renderable that threw, if known. Errors that no listener handles use the renderer’s existing error policy. See Renderer. (#1314)
  • core: CliRenderEvents.HANDLER_ERROR fires with { error, event } when a mouse handler throws. The new MouseEvent.currentTarget is the renderable whose handler is running. See Renderer. (#1313)
  • core: OTUI_STDIN_LOG writes the raw stdin bytes to a file before they are parsed. The renderer truncates the file at start. The log can contain sensitive input and is for debugging only. See Environment variables. (#1274)

Changed

  • core: Cursor reads, text measurement, audio stats, and span-list decoding use less FFI overhead. On Node, layout reads are also faster. The shape returned by TextBufferView.measureForDimensions() is now the exported type MeasureResult. (#1284, #1273)

Fixed

  • core: An immediate render during a live render loop no longer starts a second frame timer. Repeated immediate renders could multiply the timers and render far above targetFps and maxFps. See Renderer. (#1305)
  • core: The renderer keeps at most one terminal pixel-resolution query in flight. It combines resize and suspended refreshes into one follow-up query. Replies that arrive across suspend and resume no longer leak into later input. The renderer rejects pixel dimensions that are unsafe or overflow a signed 32-bit integer. See Renderer. (#1306)
  • core: On Windows, the renderer writes console output with WriteConsoleW. UTF-8 output no longer depends on the active console code page. See Renderer. (#1272)
  • core: A type: "file" import that is also a build entry point no longer fails when its default export is not a path string. The runtime uses the source path instead. See Standalone executables. (#1293)
  • react: The package imports react-reconciler/constants with an explicit .js extension. Strict Node ESM resolution no longer fails with ERR_MODULE_NOT_FOUND. See React bindings. (#1316)
  • three: The package loads Jimp only when it loads a texture or exports a screenshot. Scenes without assets load under Bun’s browser condition, where Jimp 1.6.1 failed to import. See Three.js WebGPU. (#1312)

API changes#

@opentui/core#

Added

  • class AudioCaptureStream extends EventEmitter<AudioCaptureStreamEvents>
  • class AudioCaptureStreamError extends Error
  • class AudioRecorder extends EventEmitter<AudioRecorderEvents>
  • class AudioRecorderError extends Error
  • class ImageError extends Error
  • class ImageLoadError extends Error
  • class ImageRenderable extends Renderable
  • class NativeImage
  • constructor AudioCaptureStreamError(message: string, context: AudioCaptureStreamErrorContext, cause?: unknown)
  • constructor AudioRecorderError(message: string, context: AudioRecorderErrorContext, cause?: unknown)
  • constructor ImageError(status: number)
  • constructor ImageLoadError(code: ImageLoadErrorCode, source: string, message: string, options?: { cause?: unknown; status?: number; })
  • constructor ImageRenderable(ctx: RenderContext, options: ImageRenderableOptions)
  • enum-member CliRenderEvents.HANDLER_ERROR = "handler:error"
  • enum-member CliRenderEvents.RENDER_ERROR = "render:error"
  • function imageInfo(data: Uint8Array | ArrayBuffer): ImageInfo
  • function resolveImageRenderProtocol(requested: ImageRenderProtocol, capabilities: TerminalCapabilities | null, hasResolution: boolean): Exclude<ImageRenderProtocol, "auto">
  • interface AudioCaptureDevice
  • interface AudioCaptureOptions
  • interface AudioCaptureReadResult
  • interface AudioCaptureStats
  • interface AudioCaptureStreamErrorContext
  • interface AudioCaptureStreamEvents
  • interface AudioCaptureStreamOptions extends AudioCaptureOptions
  • interface AudioCaptureStreamStats extends AudioCaptureStats
  • interface AudioRecorderErrorContext
  • interface AudioRecorderEvents
  • interface AudioRecorderStats extends AudioCaptureStats
  • interface CliRendererErrorEvent
  • interface CliRendererHandlerErrorEvent
  • interface CompositeOptions
  • interface ExtendOptions
  • interface ExtractOptions
  • interface ImageInfo
  • interface ImageLoadOptions
  • interface ImageRenderableOptions extends RenderableOptions<ImageRenderable>
  • interface MeasureResult
  • interface OwnedRawImage extends RawImage
  • interface RawImage
  • interface ResizeOptions
  • method Audio.clearCaptureDeviceSelection(): void
  • method Audio.getCaptureStats(): AudioCaptureStats | null
  • method Audio.isCapturing(): boolean
  • method Audio.listCaptureDevices(): AudioCaptureDevice[] | null
  • method Audio.openCapture(options?: AudioCaptureStreamOptions): Promise<AudioCaptureStream>
  • method Audio.readCaptureFrames(frameCount: number): AudioCaptureReadResult | null
  • method Audio.recordToFile(filePath: string, options?: AudioRecordToFileOptions): Promise<AudioRecorder>
  • method Audio.selectCaptureDevice(index: number): boolean
  • method Audio.startCapture(options?: AudioCaptureOptions): boolean
  • method Audio.stopCapture(): boolean
  • method OptimizedBuffer.drawImage(image: NativeImage, x: number, y: number, width: number, height: number, pixelWidth?: number, pixelHeight?: number, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number, protocol?: ImageRenderProtocol): boolean
  • method RootRenderable.takeCurrentRenderable(): Renderable | undefined
  • method StdinParser.hasPendingPixelResolutionResponse(): boolean
  • method StdinParser.pausePendingTimeout(): void
  • method StdinParser.resumePendingTimeout(): void
  • property AudioEngineLib.audioClearCaptureDeviceSelection: (engine: AudioEngineHandle) => void
  • property AudioEngineLib.audioGetCaptureDeviceCount: (engine: AudioEngineHandle) => number
  • property AudioEngineLib.audioGetCaptureDeviceName: (engine: AudioEngineHandle, index: number) => string
  • property AudioEngineLib.audioGetCaptureStats: (engine: AudioEngineHandle) => { status: number; stats: NativeAudioCaptureStats | null; }
  • property AudioEngineLib.audioIsCaptureDeviceDefault: (engine: AudioEngineHandle, index: number) => boolean
  • property AudioEngineLib.audioIsCaptureRunning: (engine: AudioEngineHandle) => boolean
  • property AudioEngineLib.audioReadCapture: (engine: AudioEngineHandle, outBuffer: Float32Array, frameCount: number) => { status: number; framesRead: number; }
  • property AudioEngineLib.audioRefreshCaptureDevices: (engine: AudioEngineHandle) => number
  • property AudioEngineLib.audioSelectCaptureDevice: (engine: AudioEngineHandle, index: number) => number
  • property AudioEngineLib.audioStartCapture: (engine: AudioEngineHandle, options: AudioStartOptions | undefined, channels: number, capacityFrames: number) => number
  • property AudioEngineLib.audioStopCapture: (engine: AudioEngineHandle) => number
  • property AudioEvents.captureStarted: [ ]
  • property AudioEvents.captureStopped: [ ]
  • property EnvVarConfig.required?: boolean
  • readonly property MouseEvent.currentTarget: Renderable | null
  • property RenderContext.resolution?: { width: number; height: number; } | null
  • property RenderContext.terminalHeight?: number
  • property RenderContext.terminalWidth?: number
  • property RenderLib.bufferDrawImage: (buffer: OptimizedBufferHandle, image: ImageHandle, x: number, y: number, width: number, height: number, pixelWidth: number, pixelHeight: number, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number, protocol: ImageRenderProtocol) => boolean
  • property RenderLib.imageClone: (image: ImageHandle) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageComposite: (base: ImageHandle, overlay: ImageHandle, left: number, top: number, blend: number, opacity: number) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageCopyPixels: (image: ImageHandle, destination: Uint8Array, stride: number, bgra: boolean) => number
  • property RenderLib.imageCreateFromRgba: (pixels: Uint8Array, width: number, height: number, stride: number) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageDecode: (data: Uint8Array) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageDestroy: (image: ImageHandle) => void
  • property RenderLib.imageExtend: (image: ImageHandle, top: number, right: number, bottom: number, left: number, background: Uint8Array) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageExtract: (image: ImageHandle, left: number, top: number, width: number, height: number) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageGetInfo: (image: ImageHandle) => { status: number; info: NativeImageInfo; }
  • property RenderLib.imageGetPixelsPtr: (image: ImageHandle) => Pointer | null
  • property RenderLib.imageInfo: (data: Uint8Array) => { status: number; info: NativeImageInfo; }
  • property RenderLib.imageResize: (image: ImageHandle, width: number, height: number, filter: number) => { status: number; handle: ImageHandle | null; }
  • property RenderLib.imageTransform: (image: ImageHandle, operation: number) => { status: number; handle: ImageHandle | null; }
  • readonly property RootRenderable.currentRenderable: Renderable | undefined
  • property TerminalCapabilities.image_protocol?: ImageRenderProtocol
  • type AudioCaptureStreamAction = "start" | "read" | "stop" | "stats" | "destroy"
  • type AudioCaptureStreamState = "initializing" | "capturing" | "stopping" | "stopped" | "errored" | "disposed"
  • type AudioRecordToFileOptions = AudioCaptureStreamOptions
  • type AudioRecorderAction = "open" | "start" | "read" | "write" | "stop" | "finalize" | "publish" | "stats" | "destroy"
  • type AudioRecorderState = "initializing" | "recording" | "stopping" | "stopped" | "errored" | "disposed"
  • type BlendMode = "source-over" | "source" | "destination-over"
  • type ImageColorStatus = "assumed-srgb" | "explicit-srgb"
  • type ImageErrorCode = "invalid-handle" | "unsupported-format" | "unsupported-color-space" | "malformed-data" | "dimension-limit" | "memory-limit" | "invalid-argument" | "out-of-memory" | "output-too-small" | "internal-error" | "unsupported-feature"
  • type ImageFit = "fit" | "cover" | "fill"
  • type ImageFormat = "png" | "raw-rgba" | "jpeg" | "webp" | "gif"
  • type ImageHandle = NativeHandle<"image">
  • type ImageLoadErrorCode = "file-read" | "network" | "http-status" | "unsupported-url-scheme"
  • type ImageRenderProtocol = "auto" | "kitty" | "sixel" | "blocks"
  • type ImageSource = string | URL | Uint8Array | ArrayBuffer | Blob | Response
  • type NativeAudioCaptureStats = { framesReceived: bigint; framesRead: bigint; framesDropped: bigint; sampleRate: number; channels: number; bufferedFrames: number; capacityFrames: number; }
  • type PixelFormat = "rgba8" | "bgra8"
  • type ResizeKernel = "default" | "area" | "triangle" | "cubic-bspline" | "catmull-rom" | "mitchell" | "nearest"

Changed

  • method TextBufferView.measureForDimensions(width: number, height: number): { lineCount: number; widthColsMax: number; } | nullmethod TextBufferView.measureForDimensions(width: number, height: number): MeasureResult | null
  • property RenderLib.textBufferViewMeasureForDimensions: (view: TextBufferViewHandle, width: number, height: number) => { lineCount: number; widthColsMax: number; } | nullproperty RenderLib.textBufferViewMeasureForDimensions: (view: TextBufferViewHandle, width: number, height: number) => MeasureResult | null
  • type AudioAction = "createAudioEngine" | "start" | "startMixer" | "stop" | "loadSound" | "loadSoundFile" | "unloadSound" | "group" | "play" | "stopVoice" | "setVoiceGroup" | "setGroupVolume" | "setMasterVolume" | "mixFrames" | "enableTap" | "readTapFrames" | "listPlaybackDevices" | "selectPlaybackDevice" | "clearPlaybackDeviceSelection" | "getStats"type AudioAction = "createAudioEngine" | "start" | "startMixer" | "stop" | "loadSound" | "loadSoundFile" | "unloadSound" | "group" | "play" | "stopVoice" | "setVoiceGroup" | "setGroupVolume" | "setMasterVolume" | "mixFrames" | "enableTap" | "readTapFrames" | "listPlaybackDevices" | "selectPlaybackDevice" | "clearPlaybackDeviceSelection" | "listCaptureDevices" | "selectCaptureDevice" | "clearCaptureDeviceSelection" | "startCapture" | "readCaptureFrames" | "getCaptureStats" | "stopCapture" | "getStats"

@opentui/react#

Added

  • type ImageProps = ComponentProps<ImageRenderableOptions, ImageRenderable>

Changed

  • const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; markdown: typeof MarkdownRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; scrollbox: typeof ScrollBoxRenderable; "ascii-font": typeof ASCIIFontRenderable; "tab-select": typeof TabSelectRenderable; "line-number": typeof LineNumberRenderable; span: typeof SpanRenderable; br: typeof LineBreakRenderable; b: typeof BoldSpanRenderable; strong: typeof BoldSpanRenderable; i: typeof ItalicSpanRenderable; em: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; a: typeof LinkRenderable; }const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; markdown: typeof MarkdownRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; scrollbox: typeof ScrollBoxRenderable; "ascii-font": typeof ASCIIFontRenderable; "tab-select": typeof TabSelectRenderable; "line-number": typeof LineNumberRenderable; image: typeof ImageRenderable; span: typeof SpanRenderable; br: typeof LineBreakRenderable; b: typeof BoldSpanRenderable; strong: typeof BoldSpanRenderable; i: typeof ItalicSpanRenderable; em: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; a: typeof LinkRenderable; }
  • type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : TConstructor extends RenderableConstructor<TextareaRenderable> ? NonStyledProps | "placeholder" | "initialValue" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" | "conceal" | "drawUnstyledText" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : NonStyledPropstype GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : TConstructor extends RenderableConstructor<TextareaRenderable> ? NonStyledProps | "placeholder" | "initialValue" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" | "conceal" | "drawUnstyledText" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : NonStyledProps

@opentui/react/jsx-dev-runtime#

Added

  • property JSX.IntrinsicElements.image: ImageProps

@opentui/react/jsx-runtime#

Added

  • property JSX.IntrinsicElements.image: ImageProps

@opentui/solid#

Added

  • property JSX.IntrinsicElements.image: ImageProps
  • type ImageProps = ComponentProps<ImageRenderableOptions, ImageRenderable>

Changed

  • const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; ascii_font: typeof ASCIIFontRenderable; tab_select: typeof TabSelectRenderable; scrollbox: typeof ScrollBoxRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; line_number: typeof LineNumberRenderable; markdown: typeof MarkdownRenderable; span: typeof SpanRenderable; strong: typeof BoldSpanRenderable; b: typeof BoldSpanRenderable; em: typeof ItalicSpanRenderable; i: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; br: typeof LineBreakRenderable; a: typeof LinkRenderable; }const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; ascii_font: typeof ASCIIFontRenderable; tab_select: typeof TabSelectRenderable; scrollbox: typeof ScrollBoxRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; line_number: typeof LineNumberRenderable; markdown: typeof MarkdownRenderable; image: typeof ImageRenderable; span: typeof SpanRenderable; strong: typeof BoldSpanRenderable; b: typeof BoldSpanRenderable; em: typeof ItalicSpanRenderable; i: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; br: typeof LineBreakRenderable; a: typeof LinkRenderable; }
  • type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "minLength" | "maxLength" | "placeholder" | "value" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : NonStyledPropstype GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "minLength" | "maxLength" | "placeholder" | "value" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : NonStyledProps

@opentui/solid/jsx-dev-runtime#

Added

  • property JSX.IntrinsicElements.image: ImageProps

@opentui/solid/jsx-runtime#

Added

  • property JSX.IntrinsicElements.image: ImageProps