EUI applications without a browser

Budgets

Soli's benchmark document publishes the rows it loses. This one does the same: where a number comes from a run it says so, and where it is a target it says that instead. A budget that was never measured is a slogan.

Wire — measured

From crates/eui-proto/tests/size_budget.rs. Run it yourself:

cargo test -p eui-proto --test size_budget -- --nocapture

The subject is a 50-row, four-column invoice table: 256 nodes, keyed rows, six shared style records. The HTML side is the same table with the Tailwind classes such a table really carries, unindented — the favourable case for HTML.

Bytes
Cell and header text, identical both ways2 337
EUI total4 619
EUI with the status column interned4 209
EUI, of which style records, once per session396
EUI structure only2 282
HTML total14 362
HTML structure only12 025

Total 3.1×. Structure 5.3×. 8.9 bytes of structure per node.

The text is the data — neither side can compress it away — so the honest headline is the structural ratio.

Under Content-Encoding: gzip, HTML wins

Rawgzip -9
EUI4 6191 491
HTML14 362910

That is not a typo and it is not close: compressed, the HTML is smaller. HTML's bulk is the same few tag names and class strings repeated fifty times, which is precisely what a compressor exists to remove; EUI has already removed that repetition itself, so what is left is dense and gzip finds little in it. A format that does its own deduplication competes badly against one that hands the job to zlib — and in the real world the HTML is gzipped.

So the first-paint byte comparison belongs to HTML, and anything that claims otherwise is quoting the raw column and should be corrected. Two things are unaffected, and they are the ones worth making the argument on:

  • Updates. A single cell is 22 B and re-sorting fifty rows is 201 B. Compression gives HTML no answer to that, because HTML has no update: the page is sent again, and 910 B compressed is still forty times 22 B.
  • Work. Compression makes the receiving machine's job larger — it inflates, and then still does the tolerant parse, the selector matching, the cascade, the layout of a tree whose types it inferred, and the script. That is what 00-rationale.md and the README lead with, and it is the claim that was always load-bearing.

Measured by what_the_ratio_is in crates/eui-proto/tests/size_budget.rs, which fails if either half of this reverses. The regression budget is set at 4×, below the measured 5.3×, so a real regression trips it and ordinary drift does not.

OperationMeasuredBudget
Single-cell update22 Bunder 40 B
Reversing 50 keyed rows201 Bunder 300 B
The same reversal by re-mounting4 619 B—

That last pair is the argument for MoveChild.

Runtime — targets

Not yet measurable: the client does not exist. These are what cargo run --release -p xtask -- bench will enforce.

Budget
Launch to first pixelunder 80 ms
Idle, 200 nodes0 % CPU, zero wakeups
Idle RSS, 200 nodesunder 25 MB
10 000-row virtualised table, RSSunder 45 MB
The same table, scrolling60 fps, under 2 ms CPU per frame
Client binary, stripped, two variable fonts includedunder 12 MB

Measured on 2026-09-06: 12.13 MB with the default features, 9.81 MB without accessibility (--no-default-features). The default build misses the budget by one per cent; the 2.3 MB is AccessKit and the AT-SPI bus client on Linux, and it stays in — an accessible client is the one that ships. That is where the next size work goes.

Zero wakeups is architectural, not a setting: winit runs in ControlFlow::Wait and the client redraws only when something asked it to. There is no render loop in the code to leave running by accident.

Decoder — targets

Budget
Decode a 4 KB batchunder 50 µs
Allocations per batchat most 3
Peak decode memoryat most twice the frame size

What none of this claims

Bytes on the wire were never the main prize. A 3.1× reduction matters on a slow link, but the reason EUI exists is what the client does not do with those bytes: no tolerant parse, no selector matching, no cascade resolution, no reflow of an untyped tree, no JIT. Those are the runtime numbers, and they are the ones worth judging the project on once there is a client to measure.

Rendered from doc/docs/eui/budgets.md in the repository. The normative text is spec/; where it and a prose page disagree the spec wins, and that is a bug worth reporting. Edit this page