Skip to content

Conformance

A renderer is eUI conformant when its runner replays every suite of the manifest and the report passes: every suite present, every case run, nothing failed or skipped. A surface then means the same thing on it as on ours. The same tree comes out of the reducer, every macro expands the same way, a theme resolves to the same colours, a control measures to the same box, and the host makes the same policy decisions.

The fixtures in fixtures/ are the contract. The TypeScript reference generated their expected values, and the Rust core replays them byte for byte. A renderer built on the Rust core gets the pure suites through its bindings, one built on the TypeScript package gets them from @exponential_app/eui, and anything else implements the reducer and host.json itself.

Generated from the fixtures into conformance/manifest.json, which drift-gates the case counts. A suite’s unit says what one case is.

SuiteUnitCasesA runner checks, per case
catalogcase605Reduce the case's nested node with the core catalog: no issues. Paint it under the default theme: no crash, no Unknown placeholder, every visible node painted.
macroscase315Expand the case: the tree equals expanded (JSON-equal, recipes included).
basic-mapcase13Reduce the A2UI basic surface: the tree and the issues equal the expected ones.
extensioncase4Register the extension; reduce each case: tree + issues equal; its natives reach the extension painter (an Extension leaf of that kind).
theme-resolvedtheme4Load each built-in theme: the resolved theme equals the fixture.
theme-recipescase481Resolve the part's recipe for the case's theme, props, mode and states: the visual equals the fixture.
theme-extendscase3Load the theme over its parents: the chain and the probed styles equal the fixture.
theme-invalidcase26Validate the bad theme: the same issue paths, never a crash.
control-geometrytheme × control × case400Measure the control under the theme: the box (height, padding, gap, border, radius) equals the fixture.
layoutwidth × direction4Lay the surface out with the fixture's fixed measure: every node's frame equals the fixture (exact for the Rust core and native painters, within 1 px for a CSS renderer).
overlaycase11Place the overlay: side, flip and frame equal the fixture within tolerancePx.
replaytheme × mode8Paint the kitchen sink under each built-in theme and mode: the reduced tree equals kitchen-sink.expanded.json, no Unknown, the accessibility order is the pre-order of the painted nodes.
host-transportcase20Feed the chunks through ONE decoder (JSONL / SSE), or decode the MCP result, or build the MCP action call: messages + issues equal expected.
host-policycase120Decide the function / combine / url, build the media request, parse the source, append the bind action's params to a source, negotiate the catalog ids: each equals expected.
host-routerpackage, screen or flow57Validate each package; validate each record against its kind's schema; resolve each screen (resolveScreen) and navigate target (navigateTarget, VAPP-218); for each flow build ONE router with extensionIds, install packages (issues = installIssues), route every step: the ops equal expected.
bindcase73Expand the input; for every dataset run the bind pass: the bound tree, every set press and the per-row slot cells equal the fixture (reducer issues too for extra).
style-conditionscase10Flatten the style for every context: equal to expected (key order irrelevant).
code-tokenscase23Tokenize the code: the lines of tokens equal expected.
formatcall or value86Resolve each format call through the English fallback (zoned: at the case's offsetMinutes): the string equals expected exactly; display each value in a text prop: equals expected. A platform Formatter may run too, at en-US / UTC (zoned: timeZone), comparing after U+202F and U+00A0 → space.
template-itemscase26Key the items / rows, list the template instances (path, key, accumulated suffix), reduce the surfaces (templates lifted): equal to expected.
text-directionsurface direction2Reduce the tree; every node's resolved direction and physical text alignment equal expected.
resizablecase38Normalize / resize / key / measure / convert: the numbers equal expected within 1e-6.
virtual-listcase26Window the extents, compute the scrollToIndex offset (sectioned: data index → row), group the sections, pin the sticky header: equal to expected.
animationstheme × animation38Time each animation under the theme and sample its frames (and the reduced-motion frame): equal to expected within 1e-3; opacity: the painted opacity = the node's own × the frame's, within 1e-3.
pointerwrite or read16Write each writes case: expected + its read-back read, or error with the data unchanged; read each reads case: expected.
read-onlycase4Reduce the case; the read-only pass disables exactly disabled (root, then each lifted template by key). A painting runner mounts it with readOnly: every inert id paints disabled, every live id enabled; without readOnly the disabled ids paint enabled.

Total: 2,413 cases in 26 suites (manifest version 2).

A third-party renderer (Flutter, Qt, a terminal UI) becomes conformant in four steps:

  1. Read the manifest. It lists each suite’s fixture files, the unit and the case count. It ships in the npm package as @exponential_app/eui/conformance/manifest.json, next to the fixtures.
  2. Replay every case the way the suite’s check says: reduce, expand, resolve, measure, lay out, place, paint, decode or decide, and compare with the fixture’s expected value. Geometry suites take your painter’s own measurements (controls within 0.5, a CSS layout within 1 px).
  3. Write the report (below), one entry per suite, naming every failed case.
  4. Check it. The checker compares the report with the manifest and prints the verdict:
Terminal window
bun run --filter @exponential_app/eui conformance:check path/to/report.json
# CONFORMANT my-renderer (flutter 0.12.0): 2413 cases passed in 26 suites

Outside the monorepo, checkReport() from @exponential_app/eui gives the same verdict in code. A report with a missing suite, a short case count or a skipped case fails.

One JSON object per run, validated by report.schema.json:

report.json
{
"renderer": "my-renderer",
"platform": "web",
"version": "0.12.0",
"conformanceVersion": 2,
"suites": {
"catalog": { "cases": 605, "passed": 605, "failed": [] },
"macros": { "cases": 315, "passed": 315, "failed": [] },
…
}
}
  • renderer, platform, version: who ran it, where.
  • conformanceVersion: the manifest version the runner was written against.
  • suites.<id>: cases run, passed, failed (the failed cases’ names with a short reason) and, optionally, skipped.

Our runners write it to $EXPONENTIAL_UI_CONFORMANCE_REPORT (default .conformance/<name>.json at the repo root).

The core and all six first-party renderers run the full suite. The three web renderers run it in headless Chromium, and Solid and the web component must also match React exactly: the same DOM, computed styles, frames and pixels for every case and, for Solid, every primitive specimen, compared over the whole page body.

Renderer Runner Command
eui-core (Rust core) conformance.rs cargo test -p eui-core --test conformance
@exponential_app/eui-react suites.test.ts bun run --filter @exponential_app/eui-react test:conformance
@exponential_app/eui-solid suites.test.ts bun run --filter @exponential_app/eui-solid test:conformance
@exponential_app/eui-solid (web parity with React: renderer + primitives) parity.test.ts bun run --filter @exponential_app/eui-solid test:parity
@exponential_app/eui-element suites.test.ts bun run --filter @exponential_app/eui-element test:conformance
@exponential_app/eui-element (web parity with React) parity.test.ts bun run --filter @exponential_app/eui-element test:parity
eui-gpui suites.rs cargo test -p eui-gpui --test suites
ExponentialUI (SwiftUI) ConformanceTests.swift swift test
at.exponential:eui-compose ConformanceTest.kt ./gradlew :ui-compose:testDebugUnitTest

CI (exponential-ui.yml) runs all of them on every SDK pull request and fails unless every report is conformant. A release tag is only cut from a commit where that workflow is green.

The suites lock the contract with a fixed fake measure. The real-font harness locks what real text does to it: the kitchen sink, the responsive cases and a project dashboard in both modes, rendered by React (headless Chromium) and gpui with the same committed fonts (Inter, Geist, JetBrains Mono, Nunito, Fira Code), compared frame by frame against a committed web baseline.

Step What it checks
Frame dumps Every placed part of every case (fixture × theme × mode × width × direction): frame, text, line count.
Comparison Frames within a pixel tolerance; a text may wrap one line more or less. Unexplained divergences are origins: the fix list.
Known divergences conformance-known.json: per cause, which side is right and who fixes it; the ratchet only goes down.
Pixel step The web surface beside a real gpui window: pixel ratio and SSIM. Reported only; it does not gate.
Terminal window
bun run --filter @exponential_app/eui conformance -- --only kitchen-sink/playful/dark/390/ltr

The full harness (the matrix, recording, the shared browser runner and the web-parity details) is documented in conformance/README.md.