# A2UI conformance suite

This directory holds the gating mechanism for the A2UI renderer parity
strategy described in
[`docs/proposals/a2ui-renderer-parity.md`](../../../../docs/proposals/a2ui-renderer-parity.md).

## What's here

- **`conformance-corpus.json`** — one envelope per component in
  `BASIC_CATALOG_V0_9`. The shared source of truth that every renderer
  test loads.
- **`conformance.rs`** — Rust validator + store test. Asserts every
  envelope in the corpus passes `validate_payload`,
  `validate_envelope`, and applies cleanly to a fresh
  `A2uiSurfaceStore`. Runs as part of `cargo test --workspace`.

## Adding a new component

Per CLAUDE.md hard rule #2 (FFI parity), and the parity proposal:

1. Add the component name to `supported_components()` in
   `crates/car-a2ui/src/lib.rs`.
2. Add a corpus case to `conformance-corpus.json` exercising the new
   component (a `createSurface` + `updateComponents` envelope pair, plus
   `updateDataModel` if the component has bound state).
3. Update the expected list in `conformance.rs::corpus_covers_every_basic_catalog_component`.
4. Add the renderer-side support: web (`@a2ui/lit` upstream — pin a
   matching version in `car-server/static/index.html`) and SwiftUI
   (`apps/car-a2ui-renderer/Sources/CarA2uiRenderer/A2uiRenderer.swift`).
5. Run the renderer snapshot tests (see below) and commit the new
   snapshots.

If steps 4–5 land in a separate PR from steps 1–3, the conformance
test fails for the released catalog but renderers don't yet support
the new component — that's the gating mechanism working as
intended. Don't paper over it; finish the renderer support.

## Renderer-side snapshot tests (cross-language)

The Rust test in `conformance.rs` is the *protocol* gate. Each
renderer additionally owns a *visual* gate that loads the same corpus
and snapshots its rendered output.

### Web renderer (`@a2ui/lit`)

Test lives at `car-rs/examples/a2ui-end-to-end/conformance.test.mjs`
(planned — not yet implemented). Approach:

1. `node:test` runner, no extra deps beyond `ws` (already in
   `package.json`) and `jsdom` for headless rendering.
2. Load `tests/conformance-corpus.json` from this crate.
3. For each case: instantiate the `<a2ui-surface>` Lit component,
   apply each envelope via the official `@a2ui/web_core` processor,
   wait for render, take an HTML snapshot of the resulting DOM
   subtree.
4. Snapshots go in
   `examples/a2ui-end-to-end/__snapshots__/<case>.html`.
5. CI fails on snapshot drift unless `UPDATE_SNAPSHOTS=1` is set.

### SwiftUI renderer

Test lives at
`apps/car-a2ui-renderer/Tests/CarA2uiRendererTests/ConformanceTests.swift`
(planned — not yet implemented; **blocked on the
`mlx-sys`/Xcode 26.4 Metal incompat — see task #30**). Approach:

1. XCTest case per corpus entry. Loads the JSON via `Bundle` lookup
   (the corpus file is added as a test resource in `Package.swift`).
2. Apply each envelope via the in-process `A2uiSurfaceStore` (no WS).
3. Render the resulting surface into an `NSImage` via SwiftUI's
   `ImageRenderer` API, hash the pixel buffer, compare to the stored
   reference hash.
4. Reference hashes live alongside the test as
   `__snapshots__/<case>.hash`.

Both renderer tests use the **same corpus** as the Rust test. There
is exactly one source of truth.

## Why this isn't `cargo bench` or visual regression

Visual regression testing (full image diff with a tolerance) was
considered and rejected:

- **Cross-platform pixel reproducibility is unreliable.** macOS 26 ↔
  Sonoma renders SF Symbols differently. CI runners on different OS
  versions would generate different baselines.
- **Hashes are too brittle.** A single-pixel font hint change
  invalidates the snapshot. The HTML structure check is more
  meaningful for regression purposes than pixel equality.
- **Behavioral conformance** (does the slider's value bind, does the
  modal show on `/open == true`, does the tab label come from the
  `label` prop and not the component id) matters more than visual
  fidelity for protocol parity.

The renderer tests above check structural conformance, not pixel
parity. Visual polish is reviewed by humans, not by CI.

## Cadence

Per the parity proposal: basic-catalog bumps are quarterly at most.
Each bump moves all four pieces in lockstep — Rust validator
component list, corpus, web renderer pin, SwiftUI renderer
implementation. The conformance test fails until they're in sync,
which is the point.
