Reference · Decisions
0039: libWolkit and Wolkit
A Rust engine draws the window; a Crystal shard owns views, signals and the tools; patches and events cross between them as data.
Status: accepted (main approved the plan 2026-11-03; drafted by WK-W1). Amends 0036 (the wolkit and GPU paragraphs). Builds on 0020 (libwol and the view tree), 0026 (memory saver) and 0036 (Crystal is the userland language).
On this page
Context
0036 said wolkit is a declarative view tree that maps one to one onto shared::view, drawn by the Kit host, with "a small 2D canvas over Vulkan" for acceleration and a wol_surface family in wol.h. Three limits make that too small for the lead's goal of a fast, reactive, GPU-first UI kit. The view wire carries at most 200 parts, 12 levels and ten kinds, with no custom styling, no long lists and no canvas. The whole tree crosses as one JSON line on every change. And everything the kit can do is whatever the GPL Kit host already draws, so an app cannot be faster than the host. The lead's two rulings are a Rust library with a stable C ABI that holds the engine, and a thin Crystal binding that owns the DSL, the runtime abstractions and the developer experience.
Decision
- libWolkit is the engine. A new crate
crates/wolkit(packagewolkit,libwolkit.so.1, headercrates/wolkit/include/wolkit.h), BSD-2-Clause, depending only onsharedandcore. It holds the retained scene, style, layout, text, paint, a CPU reference rasteriser and a Vulkan renderer, input routing, the accessibility tree and the animation clock. Its C ABI has 53 verbs, opaque handles, UTF-8 with lengths, a status code and an optional error sentence, one thread at a time per engine, and no callbacks. - Everything else crosses as data. An app changes a window with one binary patch stream per frame (
wolkit_window_apply, formatWKP1, interface I10) and learns what happened by draining binary event records (wolkit_engine_read_events, interface I12). Ids of node kinds, properties, ops, events, roles and state bits are append-only, listed in the header and snapshot-tested (abi/wolkit-1.0.props,abi/wolkit-1.0.h). A newer app can send an optional op to an older engine because every op is length-prefixed with a must-understand flag. New widget behaviour adds ids, not symbols. - One scene ABI, two backends per window. The kit backend lowers the scene to
shared::viewand talks to the Kit host over its existing socket, so it needs no change to the GPL side and ships first. The native backend is the engine's own window and becomes the default after a parity gate (screen reader, input method, clipboard and drag and drop, budgets, look parity, fuzzing, audit). The kit backend stays supported for at least two releases after that. A headless backend has no display and backs the conformance suite, the golden harness and the shard's test harness. One conformance suite (crates/wolkit/tests/conformance.rs) runs on every backend with a per-backend list of expected differences. - The shard owns the DSL and the user-level reactive layer.
Signal,computed,effect,Resource, components, navigation and the developer tools stay in Crystal because the values and closures live in the Crystal heap. The engine owns a render-level dirty graph (per-node dirty bits for style, layout, paint and accessibility) and the animation clock, so springs and tweens run in Rust. Cost, stated plainly: a binding for another language must write its own user-level reactive layer. 0036 already says WolOS writes no high layer for other languages. - 0036 amended in two places. wolkit is no longer "a view-tree builder mapping one to one onto
shared::view":shared::viewis the kit backend's wire only, and the shard's golden is a decoded scene dump, not view JSON. The GPU paragraph's "small 2D canvas over Vulkan" becomes the engine's owncanvasandsurfacenodes (wolkit_window_canvas_submit,wolkit_surface_*); thewol_surfacefamily is not added towol.h(it stays 1.14), because native windows are not Kit-host windows. If the lead keepswol_surfaceinwol.hfor Kit-host windows, it is added as 1.15 independent of this engine. - Boundaries.
crates/wolkitmay depend onsharedandcoreand on permissive third-party crates allowed bydeny.toml; no GPL crate depends on it (crates/shared/tests/architecture.rs).unsafeis confined tosrc/ffi/(the C boundary),src/loop_/(onepollcall),src/gpu/andsrc/window/native/, each block with a// SAFETY:comment, listed inRR-068(docs/RULES.md,crates/shared/tests/rules_rust.rs) and counted bycrates/shared/tests/unsafe_ratchet.rs(awolkitrow, provisional at creation and lowered by the closing worker). Raw slices from C pointers are built only insrc/ffi/mod.rs(bytes_in,bytes_out, behindclippy.toml's disallowed methods). Every list is bounded (docs/modules/libwolkit.md, Limits). Untrusted input is validated before it is applied and applied atomically; every decoder has a fuzz target (wolkit_patch,wolkit_wsb,wolkit_wolcss). A panic stops at the boundary asWOLKIT_ERR_INTERNAL. - Crystal rules this adds (next free
RCRids, checked bycrates/controller/tests/rules_crystal.rs):FFI_ALLOWgains the wolkit FFI files, andALLOWED_SHARDSand the shard census gaincryswolandwolkit, so an app that requireswolkitpasses RCR-043.
Consequences
the engine is a second large Rust component beside the Kit host, about 45 to 60 thousand lines by the plan's estimate (not measured), with its own text, layout and rendering to keep correct and fast. In return an app is no longer limited by the Kit's 200 parts, can scroll a hundred thousand rows, can draw, and can share a GPU device with the AI framework (wolkit_engine_get_gpu_uuid). The look of native windows must track the Kit's: both read the same token sources (design/tokens/colors.json, crates/shared/src/theme.rs, crates/shell/src/kit/contrast.css) and a drift test compares them. Two open items sit outside the engine: a compositor rule so a toplevel can ask for glass, and a hook so native windows reach the global menu bar (crates/shell/src/appmenu.rs); until they land, backdrop: glass draws the normal translucent token colour and a native window carries its own menu. The version of the ABI is 1.0 until the first release that ships it; after that a shipped symbol never changes.
Related
- 0036: Crystal is the userland languageOne language for apps, tools and integrations, with generated bindings over the stable C ABI.
- libwol: the app APIOne stable C ABI for apps in any language: windows, menus, notifications, files, power and more.
- Cryswol: every system verb in CrystalThe Crystal shard that gives an app windows, notifications, files, power, network, Search and the other system verbs, as typed objects.
- Wolkit: the UI kitViews from signals, components, the interface guideline parts, a navigator, services and a test harness, in one Crystal shard.
- libWolkit: the UI engineThe engine under Wolkit: scene, style, layout, text, paint, input and accessibility, behind a small versioned C interface.
From the developer documentation, wonderlandos/wolos docs/adr/0039-libwolkit-and-wolkit.md.