# jolt-zvui The retained-tree C ABI, in Zig, painted by [dvui](https://github.com/david-vanderson/dvui). `crates/jolt-vidya` exports this shape on egui and `crates/jolt-tui` exports it again over terminal cells. This is a third painter under the same shape: a tree the caller mutates between frames, one call that walks it, and a queue of what the person did. Nothing in the tree knows what paints it, which is the property that makes a third backend possible at all. The prefix is `zvui_` and the object is `libjoltzvui.so`, for the same reason jolt-tui's are `tui_` and `libjolttui.so`: a consumer names one object in `:jolt/native` and binds one set of symbols. ``` src/tree.zig nodes, props, children, the event queue — no dvui in it src/paint.zig one walk per frame, in dvui widgets src/lib.zig the 32 exported symbols include/zvui.h the header a C caller (or a jolt.ffi/defcfn) reads demo.c a caller: builds a tree, paints it, drains events ``` ## Building Zig 0.16 and nothing else installed — dvui, SDL3, FreeType and tree-sitter are fetched and built by the Zig package manager, at the revs `build.zig.zon` pins. ```bash zig build --release # zig-out/lib/libjoltzvui.so zig build test # the tree, without a window ``` `--release` rather than `-Doptimize=`: this library sets a preferred optimize mode (ReleaseSafe), and Zig spells that choice `--release`. ## The demo ```bash gcc demo.c ./zig-out/lib/libjoltzvui.so -Wl,-rpath,'$ORIGIN/zig-out/lib' -o demo ./demo # a window ./demo --dump # the tree on stdout, no window ./demo --frames 3 # paint three frames and leave ``` ## The vocabulary Tags: `window` `box` `hbox` `vbox` `page` `card` `frame` `scroll` `label` `title` `dim-label` `button` `checkbox` `entry` `separator` `spacer`/`gap` `progress`. An unknown tag is a container rather than an error, so a tree written against a richer painter still shows its contents here. Props: `text` `label` `orientation` `spacing` `margin` `align` `max-width` `width-request` `placeholder` `value` `active` `sensitive` `kind` `multiline` `size` `fill-height`. Events: `click` `change` `toggled` `activate`. ## Two things worth knowing **Ids come back; identities do not.** The free list hands a node id out again after a free — but widget identity is `(generation, index)`, so a node freed and reallocated in the same frame does not inherit the cursor, scroll position or animation of the node that used to live in that slot. That is the bug class frq's `deps.edn` describes from the reconciler's side, closed here at the backend rather than only upstream. **Zig cannot keep jolt-abi's unwinding rule.** There is no `catch_unwind`, so `jolt_abi::guard`'s bargain — a panicking decoder is a black tile rather than a dead process — is not available. A Zig panic aborts. The answer here is to not panic: every entry point is total, a bad node id is a miss rather than an index, and allocation failure answers the fallback a caught panic would have. The build is ReleaseSafe by default for the same reason — a trap inside this library beats quiet corruption of the caller's heap. The other two rules are kept exactly. Strings out live in one scratch slot until the next string-returning call, and nothing here calls back: events are queued and polled. ## Not done No `page`/`card` push-pop immediate API (the tree is the only interface), no images, avatars, video frames, clipboard, file picking, font loading, or Android entry points. `jolt-vidya` exports 57 symbols; this exports 32.