# Changelog All notable changes to **blenderui** (BUI). The format follows [Keep a Changelog](https://keepachangelog.com); versions follow [Semantic Versioning](https://semver.org). ## [Unreleased] ### Added - **Rust:** `` (and ``): the field reports every keystroke to `on_input` / `on_change`, not only on Enter or blur, so a view can follow the typing (a search box, a date field that lights up a calendar). ### Changed - **Text fields:** in the HTML look (browser-style inputs and text areas) the caret blinks like a browser's (about every 530 ms, solid right after it moves or the text changes); Blender's own fields keep Blender's solid caret. The window wakes only for the next toggle, so an idle window still sleeps. ### Fixed - **Browser-look text fields** take the CSS `color` and `font-size` the field itself is given (one that differs from its parent's); without them they keep Chrome's black 13.33px, as before. - **Browser-look form controls** drew their text with Blender's UI drop shadow, which made it look heavy next to the page's text. They draw none now, as in a browser. - **macOS:** the npm `bui-runtime` (and the release build of the addon) failed to link with `Undefined symbols: ___isPlatformVersionAtLeast`: the Cocoa host asked for ProMotion with `@available`, which needs a symbol from clang's runtime that a Rust binary linked with `-nodefaultlibs` does not have. It asks by selector now. ## [0.5.0] - 2026-11-08 ### Added - **Lua bindings** (`BUI_BUILD_LUA_BINDINGS`, off by default; pull requests #19 and #20 by seclorum). A standalone `bui-lua` executable (with a REPL) and a `bui.so`/`bui.dll` loadable module for stock Lua 5.4, LuaJIT or LuaRocks. Every BUI widget, event, tree view, file browser, menu (submenus included) and node editor is available from `require("bui")`, the bindings check the kit version at load time and expose `bui._VERSION`, and seven test suites (`bui_lua_*`) run under `ctest`. 26 example scripts in `bindings/lua/examples/`: `hello`, `counter`, `todos`, `gallery`, `forms`, `dashboard`, `anim`, `lua_table_editor`, `editor_screenshot`, `box`, `canvas`, `curve`, `enum`, `grid`, `histogram`, `listview`, `menubar`, `popover`, `ramp`, `split`, `textarea`, `menus`, `color_picker`, `filebrowser_demo`, `image_viewer` and `data_table`. CI builds and tests it on Linux and Windows, and it now builds on Windows (a `linenoise` stand-in and a `setenv` shim). - **npm on Linux and macOS:** the prebuilt addon and `bui-runtime` ship for Linux x64 (glibc 2.35 or later) and macOS (arm64 and x64) as well as Windows x64, one package per platform: `@blender-ui/native-` (`bindings/node/platform.js`). `@blender-ui/native` lists them as `optionalDependencies`, so an install gets only its own (about 29 MB). The install command does not change. With `--omit=optional` the addon is missing, and the load error says so. `release.yml` builds them on `ubuntu-22.04`, `macos-15`, `macos-15-intel` and Windows, then publishes the platform packages before `@blender-ui/native`. On macOS, JavaScript still runs headless. (concprog, pull request #16) - **Native Wayland windows (experimental).** `kit/intern/kit_window_wayland.c` is a Wayland host with the same `BK_window_*` API, modelled on Blender's `GHOST_SystemWayland`: xdg-shell toplevels with server-side decorations when offered, a `wl_seat` with xkbcommon (keys, key repeat), the pointer, the clipboard through `wl_data_device`, cursors, the output's buffer scale, and `wl_surface.frame` pacing into `wl_shm` buffers (only damaged rectangles are copied). It uses Blender's `wayland_dynload` (`kit/extern/wayland_dynload`), so libwayland loads at run time and nothing is linked; libxkbcommon is `dlopen()`ed. A Wayland session gets it; `BUI_WINDOW_BACKEND=x11|wayland` picks one. It draws on the CPU, so with the GPU renderer on (`BUI_GPU=1`, or `BUI_gpu_set_default(1)` before the first window) the session keeps X11 (XWayland): the X11 host and its GPU start (the worker-thread device, the idle switch) are unchanged. Builds without the Wayland headers, `wayland-scanner` and `wayland-protocols` (or with `-DBUI_WAYLAND=OFF`) are X11 only, as before. Not done: IME, client-side decorations (no title bar on GNOME), pointer warping. - **Input methods (IME) on Windows**, ported from Blender (`GHOST_ImeWin32`, the text button's IME handling in `interface_handlers.cc` and its drawing in `interface_widgets.cc`). Chinese, Japanese and Korean input now works in text fields and text areas: the composition is drawn underlined at the caret (a thicker line under the clause being converted), the candidate window follows the caret instead of the top left corner of the window (issue #14, reported by YuFu-Onship), and the result is typed in like any other text. The IME is on only while a field is being edited, so shortcuts keep working with a CJK keyboard; number fields and password fields take no IME, as in Blender. A composition that is still going when another field takes over stays in the field it was in. - **C API changes:** one platform independent IME API for every window host and binding: `BKImeEvent`, `BK_ime_event()`, `BKImeState`, `BK_ime_state()` and `BK_ime_composing()` (`BUI_kit.h`). Only the Win32 host uses it so far; X11 and Cocoa do nothing and are unchanged. `bench/ime_bench.c` measures what it costs (nothing measurable on the paths that did not change: keystroke + repaint 18.5 vs 18.4 us, idle frame 35 ns, the host's poll 7 ns). ### Changed - **Website:** the benchmarks page has a Windows / Linux switch, with the Linux numbers (start time and battery, frame times, JavaScript, React and Svelte, and Rust compile times); the docs have a Lua section, a Wayland section on the window hosts page and a macOS that is no longer marked experimental; the README's first-app and language-guide sections collapse (``). - **Linux GPU renderer start** is 50 to 80 percent sooner (the first frame no longer waits for the OpenGL driver): for example Rust 169 to 61 ms, JavaScript 207 to 101 ms, Python components 216 to 96 ms, and the same gpui code 110 to 23 ms. CPU renderer start, frame time and memory are unchanged. The numbers are in the website's benchmarks page, the Linux tab. - **Lua host:** CI builds `bui-lua` and runs its `bui_lua_*` tests on Linux and Windows (a new `lua` job); the option is in the CMake options tables (README, `docs/`, the website) and the website has a Lua page; the lowercase `find_package(lua)` fallback is config-mode only, which removes CMake's package-name warning. - **macOS is no longer labelled experimental.** Three people have tried the Cocoa host by hand and it works, so the README, the website and the docs say so (the platform tables, the FAQ, the limits, the roadmap, the landing page). What is still true there is unchanged: the CPU raster only (no Metal renderer), and JavaScript windows are headless. The native Wayland host is now the only experimental window host. - **macOS docs:** three people have now tried the Cocoa host by hand: two Apple Silicon Macs and an Intel MacBook Pro ([piaoboo](https://github.com/piaoboo), issue #17: the PyPI wheel works, the source build passes 25/25 tests). The guide and the README, website and docs that said "two Apple Silicon Macs" or "nobody has tried an Intel Mac" now say so. [DanNoby](https://github.com/DanNoby)'s 2026-11-07 log (issue #6) confirms the duplicate Objective-C class fix: `test_cocoa_classes_come_from_buipy` and the other 20 `buipy_imgui` tests pass and there is no `objc:` warning. The Xcode toolchain note now says it depends on Command Line Tools older than the SDK, not on the macOS version. - **README and website:** the FAQ says macOS has been tried by hand on two Apple Silicon Macs (it said nobody had checked), and a new FAQ entry and the roadmap list what people asked for: Vue and Angular (adapters over `@blender-ui/native`), and Lua (a community pull request is in review), Ruby and Go through cgo (over the C kit). None of them exists yet. - **Linux GPU start:** the OpenGL context and device are created on a worker thread, as on Direct3D 11. The first frames draw on the CPU. A window switches to the GPU when the device is ready, also when it is idle (Linux and Windows; `BUI_raster_gpu_pending`). (concprog, pull request #10) - **Benchmarks on Linux:** `bindings/rust/bench/common/measure.rs` measures CPU time (`CLOCK_PROCESS_CPUTIME_ID`) and memory (`/proc/self/status`) on Linux, so `bui-bench`, `bench_frames` and `cargo test` build there; Windows is unchanged. (concprog, pull request #10) ### Fixed - **Browser-look text fields** take the CSS `color` and `font-size` the field itself is given (one that differs from its parent's); without them they keep Chrome's black 13.33px, as before. - **Browser-look form controls** drew their text with Blender's UI drop shadow, which made it look heavy next to the page's text. They draw none now, as in a browser. - **Python: an intermittent crash (access violation) when a native window and Tk share a thread.** The C callbacks the kit makes into Python (host hooks, window timer and close, reference counting) ran without the GIL when a window message was dispatched from inside Tk's `update()`, which releases the GIL around Tcl and whose notifier dispatches every window message of the thread. They now take the GIL first. It showed as `buipy_tk_compat` failing about one run in ten on Windows CI (and one in forty on v0.4.1); it passed 120 runs in a row after the fix. - **Node: no window and no message.** When the addon could not get a window system (no `DISPLAY` or a display that does not answer, no Wayland socket) it fell back to headless silently, and a window that could not be created was dropped without a word: the app ran with nothing on screen and nothing in the log. The addon now prints why to stderr (and a window that cannot open is reported closed, so the app can end). `BK_window_supported` on Linux also asks Wayland only when the session prefers it, so it no longer says yes through a default Wayland socket that the window host would not use. - **Node and Bun:** a relative `BUI_NODE_ADDON` path starts at the working directory, not at the package folder. The load error now shows the cause. (concprog, pull request #16) - **Rust windows:** a dirty window with a pending frame request waits for that frame instead of a 0 ms timer, so `requestAnimationFrame` callbacks run once per frame. (concprog, pull request #10) - **X11:** a frame callback that closes its window no longer renders into the closed window. (concprog, pull request #10) - **X11:** a window that closes while its GPU device starts releases the device before the X window is destroyed (`BUI_raster_gpu_release`), so Mesa no longer prints `failed to create drawable`. (concprog, pull request #10) - **CSS colors:** `color-mix()` in `hsl`, `hwb`, `lch` and `oklch` treats the hue of white, grays, black and `transparent` as missing (CSS Color 4 powerless hue), so the mix keeps the other color's hue: `color-mix(in hsl, white, blue)` is a light blue, not pink. (HEOJUNFO, pull request #18) - **CMake subprojects:** `BUI_kit.h`'s include path is found when BUI is built as a subproject of another CMake project (Francis Giraldeau, pull request #12). - **Debug builds:** `core/intern/list_sort_impl.h` (Blender's `list_sort`) declares its helpers `inline`, which in C99 gives the linker nothing to call: a debug link failed. They are now `BLI_INLINE`, Blender's `static inline` (concprog, pull request #15). - **Windows CI:** the Python Tk-compatibility test is no longer flaky (the GIL fix above), and the image-cache test no longer requires a timing-dependent number of cancelled decodes. ### Thanks - [seclorum](https://github.com/seclorum): the Lua bindings and their examples (#19, #20). - [concprog](https://github.com/concprog): the Linux GPU start, X11 frame pacing and Linux benchmarks (#10), the debug link fix (#15), and the npm packages for Linux and macOS with the `BUI_NODE_ADDON` fix (#16). - [HEOJUNFO](https://github.com/HEOJUNFO): `color-mix()` and powerless hues (#18). - [giraldeau](https://github.com/giraldeau): building BUI as a CMake subproject (#12). - [YuFu-Onship](https://github.com/YuFu-Onship): the input method (IME) report that led to IME support (#14). - [piaoboo](https://github.com/piaoboo): the Intel Mac test report (#17). - [DanNoby](https://github.com/DanNoby): macOS testing and the Objective-C log (#6), and everyone who reported problems on Discord, including the Linux Mint user whose silent Node app led to the error messages. ## [0.4.1] - 2026-11-06 ### Added - **The C API is versioned.** `BUI_kit.h` has `BUI_KIT_VERSION_MAJOR` / `_MINOR` / `_PATCH`, `BUI_KIT_VERSION_STRING`, `BUI_KIT_VERSION` (0.4.0 is 400) and `BUI_KIT_VERSION_AT_LEAST`, the library reports its own with `BK_version()` and `BK_version_string()` (Python: `buipy.kit_version()`), and `BUI_DEPRECATED` is there for the day a function is retired. The number is BUI's own: `check_versions.py` refuses a release when the header differs, and `bump_version.py` moves it. The stability policy (no promise before 1.0, semantic versioning after, what counts as ABI) is in the header and in the C API guide. Changes that break the C API are listed here under "C API changes" from now on. - **Third-party notices** (`THIRD_PARTY_NOTICES.md`), shipped with the wheels, the npm packages and the crates. ### Changed - **License, following Blender's own wording:** BUI as a whole is under the GNU GPL, version 3 or later (the `LICENSE` file was already the GPLv3 text), and individual files keep their compatible `GPL-2.0-or-later` header, as Blender's do. The Python package, the Rust crates, the npm packages, the README and the website said `GPL-2.0-or-later` for the whole; they now say `GPL-3.0-or-later`. - **README:** a lead-in sentence before each screenshot that isn't the output of the snippet above it, plainer headings ("Pick your language", "Python", "Rust: HTML and CSS, without a browser"), and a corrected "Who wrote it?" (the code is written by agents, a human directs and reviews it). - **Limitations, said plainly:** a list in the README (Status and roadmap) and a new website page, covering accessibility (none yet), text and input methods, the non-native look, GPU backends, platforms, retained mode with no visual editor, and that the numbers come from one machine. The FAQ answers "is it immediate mode?" and "is it accessible?"; accessibility joins the roadmap. - **macOS:** the testing guide now lists both testers ([Krish Kalaria](https://github.com/krishkalaria12): issue #3, fixed by pull request #4; [DanNoby](https://github.com/DanNoby): 17/17 tests, issue #6), explains the Command Line Tools versus SDK mismatch (`ld: tapi error: malformed file`: select the full Xcode toolchain and delete `build/`), and records a bug from his log (fixed below: Objective-C printed "Class BUIKitView is implemented in both"). The docs and the website say two Apple Silicon Macs. - **Python:** Dear ImGui (`blenderui.imgui`) is compiled into the `buipy` extension; the separate `_buiimgui` extension is gone (the module is still named `_buiimgui`, created by `buipy.imgui_module()`). The public API is unchanged. - **CI:** the Apple Silicon runner is `macos-15` (GitHub retired `macos-14`). - **Releases:** `tools/bump_version.py --npm patch` moves every published npm package by that kind from its own version (native 0.3.0 to 0.3.1, svelte 0.1.0 to 0.1.1; a package that isn't on npm yet keeps its first version), and `release.yml` skips a package that is not on npm yet with a clear warning instead of failing (a new package's first publish is manual, then it gets a trusted publisher; `tools/release.py` shows it as MANUAL). A release is also refused when `THIRD_PARTY_NOTICES.md` is out of date. - **Svelte** now publishes from CI like the other npm packages (its trusted publisher is set up). ### Fixed - **Browser-look text fields** take the CSS `color` and `font-size` the field itself is given (one that differs from its parent's); without them they keep Chrome's black 13.33px, as before. - **Browser-look form controls** drew their text with Blender's UI drop shadow, which made it look heavy next to the page's text. They draw none now, as in a browser. - **Backspace over a Blender value resets it to its default, not to zero** (number fields, sliders, vector fields, checkboxes; the right-click "Reset to Default Value" already did, and the docs always said Backspace resets to the default). A widget's default is the value it was made with, or an explicit `default`, in every language: the kit records the first value a widget gets as its default (`NF_DEFAULT_SET`) and the dead "zero" flag of `reset` is gone. Text fields still clear. - **Website:** the interactive panel on the home page is labelled as a browser sketch, and it now does what the text next to it says: dragging down across X, Y and Z edits all three, and Ctrl+C / Ctrl+V over a field copy and paste a value. - **macOS:** Python printed `objc: Class BUIKitView is implemented in both buipy... and _buiimgui...` (also `BUIKitNSWindow`, `BUIKitWindowDelegate`, `BUIKitAppDelegate`) when both extensions were loaded: each linked the whole kit, Cocoa host included, so the classes were registered twice (reported by [DanNoby](https://github.com/DanNoby), issue #6). A process now holds one kit: `buipy` and Dear ImGui are one binary on every platform, which also means one theme and one set of kit state instead of two. ## [0.4.0] - 2026-11-06 ### Added - **Video, in every language** (`kit/intern/image`: `video.c`, `movie*.c`). One codec interface for GIF, WebP and video (Blender's MovieReader shape: open, output size, next, seek, close) and one clock (`play.c`: pause, seek, rate, loop, end). The OS decodes, loaded at run time so a machine without it still starts: Windows Media Foundation (`IMFSourceReader` with a DXVA Direct3D 11 device manager; decoding and scaling on the GPU), Linux GStreamer (`decodebin ! videoconvert ! videoscale ! appsink`, `dlopen`ed; VA-API through its `va` plugin), macOS AVFoundation (written, **untested**: no Mac). Frames are NV12 at the size drawn, two per player reused (no allocation per frame), in one GPU texture pair per player updated in place (YCbCr to RGB in the Direct3D 11 and OpenGL shaders; the software raster converts what it draws); off screen, clipped or in a window that isn't rendering, nothing is decoded. C: `BK_video_*` (new, play, pause, seek, rate, loop, poster, controls, state, time, duration, size, listener, texture, backends), the `"video"` node with Blender-style controls (play/pause button, scrubber with the time; `BK_node_set_video`), canvas items' `video`. Rust: ``, `video().video(handle)`, `blenderui::Video` (play, pause, seek, set_rate, set_loop, time, duration, state, `on_state`, texture), `Painter::video`. Python: `bui.Video` and the `Video` component (`controls`, `on_ended`). React / @blender-ui/native / `bui build`: ``. Dear ImGui: `BK_video_texture`. Muted autoplay by default; no audio yet. - **GPU NV12 pixmaps and stream textures**: `BuiPixmap.format` (`BUI_PIXMAP_NV12_BT601` / `_BT709`) and `.stream` (frames of one stream update one texture in place), and `BUI_pixmap_read`. The GPU parity test covers them (Direct3D 11 and OpenGL). - **Pay only for what you use: one feature table** (`features.json`): image formats, video, the GPU renderers, FreeType, the file browser and Dear ImGui are CMake options (`features.cmake`), cargo features of `blenderui-sys` and `blenderui` (same names; build.rs maps them and checks the list), and what `bui build` reads. Off, a part isn't compiled; its API stays. `bui build` links the smallest prebuilt runtime (`base`, `media`, `full`) with the features an app uses: `--features`, then package.json `"bui": {"features": [...]}`, then detection in the bundle (``, media file extensions, `gpu:`, file dialogs). - `bench/video` (`bui_video_bench`, `make_clips.sh`) and `tests/video_test.c`, `bindings/rust/blenderui/tests/video.rs` (counting allocator), `features.rs`, `bindings/python/tests/test_video.py`. - **Images, once in C for every binding** (`kit/intern/image`, after Blender's imbuf: one image type, `BKImBuf`, and a format table): PNG, GIF (animated), BMP, TGA and QOI with Wuffs (memory-safe), JPEG with libjpeg-turbo 3.1.2 (EXIF orientation, CMYK, DCT-scaled thumbnails), WebP with libwebp 1.6.0 (lossy, lossless, animated), Radiance HDR (after Blender's), PNM (ASCII and binary) and SVG (the kit's nanosvg; `BUI_IMAGE_SVG`, on). Decoded at the size drawn into premultiplied pixels, on a worker pool (visible first, prefetch, cancellation), into one memory-budgeted cache (`BK_image_set_budget`, 256 MiB); an arriving image redraws only its rectangle, and layout uses the header's size. C: `BK_imbuf_*`, `BK_image_*`, `BK_node_set_image_source`, `BK_node_set_imbuf`, canvas items with `src` / `imbuf` / `fit`. Rust: `` and CSS background images in every format, `Image::open` / `decode` / `from_rgba` / `texture`, `Painter::image_file(_cover)`, `image_stats`, `set_image_budget`, an image column in the data table (`CellRender::Image`), chat-list media (`Record::media`), and a `PaintSink::image`. Python: `blenderui.image_decode` / `image_probe` / `image_texture` / `texture_free` / `image_stats` / `set_image_budget`, the `Image` component, and tkinter `PhotoImage` reading JPEG, WebP, BMP, TGA, QOI, HDR and SVG too (`format="gif -index n"` picks a frame). Dear ImGui: textures from images (`image_texture`, `BK_imbuf_texture`). - Blender's `MEM_get_memory_in_use` / `MEM_get_memory_blocks_in_use` leak counters, and its condition variables and thread pool (`BLI_condition_*`, `BLI_threadpool_*`) in core. - `bench/images`: the decoder comparison and the thumbnail-grid benchmark (`bui_image_bench`). - **Animated GIF and WebP play**, decoded one frame at a time at the size drawn (the cache holds the shown frame, the next one and the decoder; no all-frames limit), each frame for its delay, looping as the file says (GIF disposal and blending, WebP's canvas). Frames advance on the context's frame timer and repaint only their rectangle; an animation off screen, clipped away, in a row that isn't shown or in a window that isn't rendering pauses (no decoding, timers or renders). `frame` holds one frame: the `` attribute (Rust, React), `Painter::image_file_frame`, `Image(frame=)`, `BK_node_set_image_frame`, canvas items' `frame`. Reduced motion holds them all: `blenderui::set_reduced_motion` (CSS `prefers-reduced-motion: reduce` then matches), `bui.set_reduced_motion`, `BK_set_animations(0)`. Stepping animations yourself (Dear ImGui): `BK_anim_open` / `BK_anim_next` / `BK_imbuf_texture_update`, Rust `blenderui::Animation` and `Image::update_texture`, Python `blenderui.AnimatedTexture`. One frame alone: `BK_imbuf_decode_frame`, `image_decode_frame` (tkinter `-index n` uses it). - **Images over 2047 px on the GPU**: a texture of their own on Direct3D 11 and OpenGL (tiled only past the device limit; `BUI_GPU_MAX_TEXTURE` forces it for tests), freed when the kit frees the image (`BUI_image_forget`) or after 120 unused frames, within 256 MiB. The parity test compares them with the CPU raster, whole and tiled. - `BKImageStats.decode_ms` (time the workers spent decoding) and `MEM_get_allocations_total`. - `bench/images`: `bui_image_bench anim `, many animations at once. - **Svelte** (new npm package `@blender-ui/svelte` 0.1.0, Svelte 5.57): Svelte 5 components in native windows, with feature parity with `@blender-ui/react`: `render(Component, options)`, `createRoot(window)`, `act(fn)`, Blender's ten widgets as components, BUI's signals (each a Svelte store, `$count`), headless tests and screenshots, Node.js (`--import @blender-ui/svelte/register`), Bun (`--preload`) and `bui build` / `bui run`. Runes, snippets, bindings, keyed `{#each}`, `{#await}`, `{#key}`, context, lifecycle and scoped `