← All posts

Rux v0.5.0: tools, not just a runtime

Rux gets a command line. rux check reports problems as path:line:col, rux fmt formats a file, and both work in CI and in VS Code with no window and no GPU. Plus a soft keyboard on phones, and diagnostics in the playground.

Rux v0.5.0 is tagged. It adds two commands, rux check and rux fmt, that work without a window or a GPU, so a Rux file can now be checked in CI and formatted by your editor. VS Code reports problems as you open and save. The playground shows the same diagnostics the desktop window does, each one a button that jumps to the line. And on a phone, tapping a text input raises the keyboard, which it did not before.

That is the announcement. What follows is how each of those went, including the one that shipped wrong and had to be fixed before the tag.

Where this sits

Each release so far has moved one thing. v0.3 made updates cheap. v0.4 made the CSS you would actually write work, and put the runtime in a browser so Rux could be tried without being installed.

v0.4 is therefore where people first showed up. This release is about what they hit within minutes of arriving: there was no way to ask a file what was wrong with it except to open a window and look, no way to format one, no way to put either in CI, and on a phone the text inputs were decorative.

So v0.5 is the first release that is mostly not about the runtime. It is about everything around it.

Using it

cargo install --git https://github.com/Aine-dickson/rux ruxlang

rux check .                 # path:line:col: severity: message
rux check --deny-warnings . # warnings fail too
rux fmt .                   # format in place
rux fmt --check .           # verify only, exits non-zero

rux check exits 0 when clean, 1 when the file has problems, and 2 when the request itself was wrong, so a CI job can tell "your code is broken" from "your pipeline is broken". Warnings do not fail a build unless you ask them to: a document that renders should not break someone's build over a CSS property Rux has not got to yet.

rux fmt also takes -, reading stdin and writing stdout, which is what an editor needs, because the buffer wanting formatting is usually unsaved. On failure it writes nothing rather than half a document for an editor to paste over your work.

Both are wired into the VS Code extension, which now formats on Shift+Alt+F and reports problems on open and save. It needs the rux binary on your PATH, or the rux.path setting pointed at it.

What landed

A checker that cannot disagree with the runtime. rux check loads a file through the same code the window does. This is worth more than it sounds: a checker written separately drifts, and then it is accepting files that do not run and rejecting files that do. The cost of doing it this way is that rux check finds exactly what the runtime finds, no more, which is why it is a checker and not a type checker.

One asymmetry is deliberate. Walking a directory skips components, because a component's props come from whoever uses it, so checking one on its own reports every prop as an undefined variable. Naming a component explicitly still checks it.

One formatter instead of two. The VS Code extension used to carry its own JavaScript re-indenter, a second implementation of rules that already existed in Rust. The two drifted inside a week: the JS copy had inherited HTML's list of void tags, which contains img but not Rux's <image>, so an <image src="…"> written without a closing slash over-indented everything after it. The copy is deleted. There is now one definition of what formatted Rux looks like, and that bug is a test.

The extension needed no TypeScript build to do this, which was the surprise. Shelling out to two subcommands was enough.

Warnings that say where. A warning used to be a bare string, which is why the old overlay could tell you what was wrong without telling you where. CSS warnings now carry a line, counted in your file rather than inside the <style> block. The dev overlay can also be dismissed by tapping it, and remembers the dismissal against the specific problems it was showing, so hiding some warnings and then introducing a parse error brings the panel straight back rather than leaving the window quiet.

The playground caught up. It had been running the v0.3 runtime, which reported an error message and nothing else: no line, no warnings. Since the playground is where most people meet Rux, that was the worst possible place to be behind. It now lists everything wrong with the document, and every item that knows its line is a button that selects that line in the editor. It runs on load as well as on Run, because a shared link carries its source in the URL, so the first thing on screen may already be someone else's broken file.

A keyboard on phones. The report was that tapping a text input in the playground did nothing. There were two separate causes.

The desktop shell had no IME at all. It never asked for composition and never handled the events, which meant dead keys, accents and CJK had never worked on the desktop either. That is now fixed, with the in-progress composition underlined while you type it.

The browser side is stranger. A browser raises a phone's keyboard for a focused DOM element, and Rux paints into a <canvas>, which is not one. So the shell keeps a real hidden <input> positioned over whichever field has focus, with pointer-events: none so taps still reach the canvas. It holds the real text, which hands composition, autocorrect, dictation and backspace to the browser instead of reimplementing four hard things badly.

The trap there is worth writing down for anyone doing the same: a browser counts caret positions in UTF-16 code units and Rux indexes bytes, and the two agree only on ASCII. Getting it wrong does not move the caret by one, it panics on the first slice that lands inside a character.

220 tests pass.

The bug only looking would find

Every release so far has had one, and this one was in the headline feature. Warnings carry a line now. The line was wrong.

lightningcss records a source position for a CSS rule and none for the declarations inside it. So every warning reported the line of the rule's selector. On a stylesheet written the way people write them, with one declaration per line, that is not a small error: a probe with three unhonored properties on lines 15, 16 and 21 reported them as 9, 9 and 18. Never the right line, twice the same line, and every one pointing somewhere the property being complained about does not appear.

The test suite was green through it, and the test was not lazy. It covers all three kinds of CSS warning and even asserts that each reported line really does contain the thing complained about. It passed because every rule in its fixture was written on one line, .b { float: left; }, where the selector and the declaration are the same number. A single-line fixture cannot tell those two apart, and nothing but opening the window and reading the panel would have said so.

The fix scans the source forward from the rule to find the declaration, stopping at the closing brace so an absent property borrows no line from the next rule.

A smaller one, in the same family: pointing rux fmt --check at the repository's own examples failed on all 28 of them. Half differed on indent width and half on CSS the formatter inlines. The examples have been reformatted rather than the formatter loosened. A formatter that disagrees with every example shipped beside it is not one to hand anybody.

What this release is not

Rux is still not on crates.io. The v0.4 post said this release would publish and it does not. Installation is still cargo install --git, or a binary from the release page. The blocker is real rather than cosmetic: cargo publish rejects any path dependency without a version, and 24 of them do not have one. Publishing is also a one-way door, since yanking a version hides it without freeing the name or letting anything replace it. It is the next thing, on a day when it is the only thing.

The soft keyboard has never met a phone. It was built from a bug report, driven in a desktop window, and exercised through browser touch emulation against the real build. Emulation cannot make a keyboard physically rise. The last release's post argued that "no hardware to test it on" is a reason to distrust a path rather than to call it done, so it is recorded here as an open question and not as a finished feature.

Warnings from failed expressions and from a component's CSS still carry no line. That is deliberate, since a warning records no file and a line from the wrong file's numbering would point confidently at the wrong place, but it means the overlay can still say what without saying where.

rux check will not catch a typo in a property lookup. rhai returns nothing for a missing map key, so {{ user.nmae }} renders empty and silently, and the checker is as blind to it as the runtime.

Unchanged, 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 keys and reconciles by count. The reactive tier has no effects or computed values. Mobile has been in the README since the first commit and there is still no mobile code in the repository.

Next

crates.io, on its own. Then v0.6, which is about applications bigger than one screen: external CSS files, component slots and events, a router, keyed r-for, and computed values.