Rux v0.1.0 — it runs
The first release: a window, real CSS layout, text, inputs, scrolling, and hot reload. Plus the lesson that cost the most — every bug that mattered was invisible to the test suite.
Rux v0.1.0 is tagged. It opens a native window, lays a .rux file out with real
flexbox and CSS grid, shapes text, edits inputs with a blinking caret, scrolls
lists, draws images, and reloads live when you save. No browser, no webview, no
JavaScript.
That's the announcement. The more useful part of this post is what building it taught, so let's do both.
What v0.1 is
Nine milestones' worth of runtime, in ten crates and about 6,000 lines:
- Layout — flexbox and CSS grid via taffy, the full box model
(per-side padding/margin/border, shorthands, longhand overrides), sizing in
px/%/rem/vw/vh, andminmax(0, 1fr)grid tracks. - Text — parley shaping with system fonts,
font-weight,text-align,overflow-wrap/word-break, and leading trimmed so text hugs its box. - Inputs —
<input r-model="sig">is a real text field: tap to focus, click-to-position, arrows, Home/End, Backspace/Delete, and a caret that blinks at 530 ms and survives the rebuild after every keystroke. Plus checkbox and radio as tap-toggles. - Scrolling —
overflow: autoscrolls with the wheel, and offsets survive a rebuild, so tapping a row doesn't jump the list to the top. - Images, opacity, clipping, HiDPI, and a recursive file watcher so imported components hot-reload too.
- Signals driving
{{ }}bindings,r-for,r-if,r-model.
56 tests pass. Every example has been driven on screen — which brings us to the thing worth writing down.
The lesson: tests didn't catch anything that mattered
For most of the build, Rux was verified by cargo test. The suite was green and
growing, and it inspired a confidence that turned out to be completely
unearned. The first time the examples were actually run and looked at, bugs
fell out within seconds — and every single one had been invisible to the tests:
- Text re-wrapped over its own siblings.
measurereturned a fractional natural width (98.001px), taffy rounded the box down to 98px, and paint re-wrapped the text at the box width it was given — breaking the last word onto a line the box had no height for. It looked random because it depended on which way each string's width happened to round. The fix is oneceil()anddisable_rounding(); the invariant is a text box must never be narrower than the text measured. - Boxes burst out of their parents. A box with no width took its full
max-content size, so a row of three 320px cards inside a 320px card laid out at
976px and hung off the side. "Hug" had to mean CSS
fit-content, not max-content. - A wrapping grid mis-measured its own height, reserving one row while
painting two, so the thing below rode up over it. That one is a taffy bug that
is still present in 0.12; Rux works around it in the
to_taffymapping. @tapinsider-forsilently did nothing.@tap="picked = item"couldn't see the loop variable, because handlers run later in global scope whereitemno longer exists. Loop bindings are now baked into the handler as aletprelude at build time. Nothing in the suite ever tested a handler that used a loop variable.- The caret stayed in the input you'd just left. The restore pass that puts the caret back after a rebuild only ever set a caret — it never cleared one. The test asserted the caret appeared in the focused input; it never asserted it disappeared from the other.
That last one is the pattern: the test checked the positive case and stopped. Tests protect against regression. They do not tell you it works. A feature is not done until it has been driven in the window — that's now the rule, and it has paid for itself in every release since.
The renderer upgrade that looked like a disaster
v0.1 needed OverflowWrap, WordBreak and a real text Cursor, none of which
parley 0.2 had. So: vello 0.3 → 0.9, parley 0.2 → 0.11.
It exploded immediately with wgpu-hal errors that looked exactly like a
windows-crate version split — the kind of dependency knot that eats a day. It
wasn't. It was a stale lockfile: an old gpu-allocator pinned in place.
cargo update -p gpu-allocator fixed the whole thing. The real API churn was
confined to three crates, and the one genuine trap was that vello 0.9 dropped
render_to_surface — you now render into the surface's intermediate target and
blit, because rendering straight to a surface texture panics (Bgra8 vs Rgba8
storage).
What v0.1 is not
Being clear about this, because a first release invites the wrong expectations:
- No text selection and no clipboard. You can edit a field; you can't select in it, copy from it, or paste into it.
- No
selectortextarea, and checkboxes and radios can't be reached by keyboard. - Scrolling is wheel-only and vertical-only. No scrollbars, no drag, no keyboard, no horizontal.
- CSS has real holes — no variables, no
@media, no pseudo-classes, and no way to pick a font at all. Worse, unknown properties are silently ignored, which is the worst failure mode the project has. - A signal change rebuilds the whole tree. Fine at these sizes, but it's why every piece of ephemeral state needs restoring by hand.
- Cross-DPI dragging is unverified — the machine here has one monitor.
And the honest framing question, which a group of developers put bluntly when they saw it: this isn't novel. Flutter, Slint and Lynx all render GPU UI without a browser. That's true, and worth repeating on the front page rather than hiding: Rux's only real claim is the ergonomic one — literal CSS, pure Rust, no DSL, no JS. Whether that combination is genuinely nicer is the thing v0.1 exists to find out.
Next
v0.2: text selection and clipboard, the last two input types, scrolling polish,
and a serious CSS push — starting with font-family, because "you cannot choose a
font" is the most visible gap on the list.