Rux v0.4.0: the CSS you actually write, and Rux in a browser
Pseudo-classes, custom properties and @media close the three gaps that made real stylesheets impossible. A dev overlay means a broken file says so instead of opening blank. And the whole runtime now compiles to WebAssembly, so you can try Rux without installing it.
v0.3 made updates cheap. v0.4 is about the two things that were stopping anyone else from using this: the CSS you would actually write did not work, and when something was wrong Rux said nothing. Both are fixed. On top of that the runtime now compiles to WebAssembly, so there is a playground you can open instead of a repository you have to clone.
What landed
Pseudo-classes. :hover, :focus, :active and :checked all match. They
stack, they carry class-level specificity, and they work anywhere in a chain, so
.card:hover .title recolours the title while the pointer is over the card. The
interesting part is what it costs: a node that any pointer-state rule could match
is tagged with its tree path, and the layout emits a region only for those. A
document with no :hover rules emits none and pays nothing. When the pointer
moves, only the subtree where the old and new chains diverge is re-cascaded,
reusing v0.3's node splice, so a caret or a scroll position elsewhere survives by
node identity.
Custom properties and var(). --name declarations inherit like color,
and substitution happens after the cascade and inline styles have merged. That
ordering is the whole trick: every property supports variables without its parser
knowing they exist, including in style= and :style. Fallbacks, variables
defined from other variables, and per-subtree overrides all work.
examples/theme.rux swaps an entire palette with one :class.
@media queries. Rules inside a block that does not match are never emitted,
so matching, cascade and specificity are untouched. A resize that crosses no
breakpoint re-cascades nothing, and a document without @media never re-cascades
at all. One landmine worth writing down: lightningcss normalises
(min-width: 600px) into the Level 4 range form (width >= 600px) before we see
it, so the range spelling is the one that actually arrives and the min-/max-
arm is the compatibility path, not the main one.
A dev overlay. A broken file no longer opens an empty window. The failure is
painted above everything, with line and column offset onto the file's own
numbering so it matches your editor's gutter. A failed hot-reload keeps the last
good tree on screen and marks it stale, so a typo mid-edit neither blanks the
window nor slips past. Warnings are collected rather than only printed:
unhonored properties, unknown pseudo-classes, undefined variables, unsupported
media conditions, and expressions that failed to compile. Every shipped example
is now asserted to load warning-free, so a noisy overlay in examples/ is a test
failure.
Accessibility. role= used to mean something to selectors and nothing to
anyone else. Rux now publishes a real accessibility tree through accesskit:
roles, names, values, checked state and on-screen bounds. Names come from
label=, else a linked <text for="…">, else a placeholder; a button is named
by the text inside it. The tree is republished per frame but only while assistive
technology is actually attached, so it costs nothing the rest of the time. It was
verified by querying the live UI Automation tree, which is what activates the
adapter, so that is a genuine end-to-end check rather than a mock.
Rux runs in a browser. The whole runtime compiles to wasm32, and
the playground runs it on a canvas via WebGPU.
The thing worth saying is what it is not: it is not a re-implementation. The
canvas drives the same shell the desktop window drives, so input, focus, the
caret, selection and scrolling cannot drift from the real runtime, because there
is only one of them. Getting there needed six changes, all cfg-gated: no
filesystem, no blocking on the main thread, no OS clipboard, and no system fonts,
which is the nastiest of them. A browser exposes no font source at all, so
fontique finds nothing, every string measures to zero, and you get a blank canvas
with no error anywhere. The bundle embeds a font for exactly that reason.
Alongside it: a five-chapter guide that builds a task list from an empty file, with every snippet checked by the test suite; the playground's syntax colouring, which runs the same TextMate grammar VS Code and this site use rather than a third copy; and a formatter that indents markup and pretty-prints CSS.
The bug I only found by looking
Thirty-seven test binaries were green. A finger could not tap anything.
Touch was implemented for dragging content and nothing else: it recorded a position, scrolled on move, and cleared on release. It never dispatched a tap. So on any touchscreen, every button, checkbox and text field was dead. The reference doc had carried "untested: no touch hardware here" for weeks, and I had read that line many times as a caveat rather than as a warning.
The playground is what found it. Rux went in front of a phone for the first time and the bug surfaced in under a minute, along with a canvas sized 420 pixels wide on a 360 pixel screen. Neither was reachable by any test I could run.
The lesson is a corollary of the one this project keeps relearning. The standing rule is that a feature is not done until it has been driven in the window. The corollary is that "there is no hardware to test it on" is a reason to distrust a path, not a reason to call it done. Anything marked untested should be read as probably broken.
There was a second one, in the same family. The pointer leaving the window fires
CursorLeft, not a CursorMoved, so a hovered element stayed lit after the
pointer was gone. Set but never cleared, which is the exact shape of v0.1's worst
bug. It now has a test that asserts the clearing, not the setting.
What this release is not
The overlay cannot be dismissed, and CSS warnings have no line numbers yet, so it
tells you what is wrong without always telling you where. There is no
rux check, so none of those diagnostics are available to CI or an editor.
The accessibility tree is flat, and action requests from assistive technology are received but not dispatched. Screen readers can read a Rux window; they cannot yet drive it.
One hole is not fixable from outside the engine: rhai returns () for a missing
map property, so {{ user.nmae }} still renders empty and silently. That is
precisely the failure the overlay work exists to kill, and it is now the second
recorded motivator for forking rhai, beside the older one that no function can
mutate a signal.
The synthetic checked class still works alongside :checked for one more
release, then goes.
The playground needs WebGPU, and it is a browser on a phone, not a native mobile app. Mobile has been in the README since the first commit and there is still not one line of mobile code in the repository. That gap is now scheduled rather than implied.
Unchanged from v0.3 and still the largest gap: there is no true inline text flow,
so two <text> elements cannot share a line and bold inside a sentence is not
expressible. A component takes props but cannot emit events or render children.
r-for has no index and reconciles by count rather than by key. The reactive
tier still has no effects or computed values.
Next
v0.5 is about being usable by someone who is not me: publishing to crates.io,
rux fmt and rux check as real subcommands, and the playground catching up to
the overlay this release just built. The
roadmap now plans every version through 1.0.