Measured
What works
The success side of the pair. 54 components from 6 third-party libraries across five styling architectures were run through one pipeline and scored against the original package rendering: 89.6% mean computed-style equality, exact string match, no tolerance, over 379,861 style cells. In the other direction a 599-variant Figma kit converted to code scores 92.70% over the 537 statically scorable variants. What that does not say: those 54 components are 6.0% of the 893 components in the libraries they came from, and they were picked because they were the tractable ones.
Read this page with its companion. This one is the shorter of the two, and it is the one written by the interested party. docs/23-known-limitations.md — Known Limitations is the complete inventory of what this tool does not do: coverage, the component classes captured nowhere, what a captured component fails to reproduce, the journey verbs that do not exist, and what each gate leaves out of its denominator. If you are deciding whether to adopt this, read that one too — start with its coverage section, which opens on the same 6.0% denominator this page does.
libraries are measured here — the denominator
over 54 captured components
over 537 scored variants
round trip closed — not lossless
(
npm run eval)(
evals/golden.json)Every figure above and below is parsed at build time out of docs/24-what-works.md, which is generated from the committed artifacts — no number on this page is typed in, and the coverage figure is printed at the same size as the flattering ones on purpose.
The denominator, first Generated
Every mean below is an average over captured components. This is which components, and what fraction of each library they are — printed before the results rather than after them.
| library | contracts committed | measured (has a scorecard) | library size | coverage |
|---|---|---|---|---|
Altitude (altitude-web-components@1.0.2) | 8 | 8 | 67 | 11.9% |
Astryx (@astryxdesign/core@0.1.6) | 13 | 5 | 222 | 2.3% |
Carbon (@carbon/react@1.112.0) | 10 | 10 | 243 | 4.1% |
MUI (@mui/material@9.2.0) | 14 | 14 | 135 | 10.4% |
Polaris (@shopify/polaris@13.9.5) | 12 | 12 | 180 | 6.7% |
Flowbite / Tailwind (flowbite-react@0.12.17) | 5 | 5 | 46 | 10.9% |
| total | 62 | 54 | 893 | 6.0% |
Read every percentage on this page as “on the easy 6.0%.” The 54 measured components were chosen because they were tractable, not at random — Button, Badge, Chip, Card, Checkbox, Tag, Avatar, Divider and their siblings. Data grid, tree, virtualized list, date picker, rich text and charts appear in zero committed contracts. A mean over this slice is a statement about this slice.
The size denominators are the one set of numbers here that is not machine-derived: they come from one-off extractor runs recorded in docs/22 §8.3, and they count helpers and utility directories, so they lean against us. The true percentages are a little higher; the order of magnitude is the finding. This page also uses the stricter column — components with a measured scorecard (54), not contracts committed (62) — because the fidelity numbers describe only the measured ones.
Three more corpora exist and are deliberately not counted above, because they are not third-party captures:
| corpus | contracts | what it is | source |
|---|---|---|---|
| this repo's own library | 51 | hand-authored here; the code→design direction's fixture | contracts/*.contract.json |
| Untitled UI (Figma kit) | 30 | proposed FROM a canvas, not extracted from code — see §4 | examples/untitled-ui/storybook/contracts/ |
| Eventz (Figma kit) | 17 | a designer's own file with real variable names | examples/eventz-vars/contracts/ |
Fidelity — code → contract → rendering Generated
The measurement: an enriched contract is emitted to HTML by core/emit-html and compared against the original npm package rendering in the same pinned Chromium, per prop combination × interaction state. Computed-style equality is an exact string match over the styled channel set, with no tolerance and no whitelist — the browser's full longhand set is enumerated, and a channel the pipeline never opened still counts against it.
| library | components | mean %equal | median | ≥90% | ≥80% | cells compared | cell-weighted |
|---|---|---|---|---|---|---|---|
| Altitude | 8 | 91.7 | 94.0 | 5/8 | 8/8 | 6,224 | 90.2 |
| Astryx | 5 | 93.1 | 98.6 | 4/5 | 4/5 | 18,656 | 95.9 |
| Carbon | 10 | 87.5 | 86.7 | 4/10 | 8/10 | 54,297 | 82.8 |
| MUI | 14 | 89.7 | 91.3 | 9/14 | 12/14 | 89,851 | 85.4 |
| Polaris | 12 | 89.4 | 91.5 | 7/12 | 11/12 | 200,405 | 90.5 |
| Flowbite / Tailwind | 5 | 86.8 | 88.9 | 2/5 | 4/5 | 10,428 | 94.2 |
| all six | 54 | 89.6 | 90.7 | 31/54 | 47/54 | 379,861 | 88.6 |
Two means, both printed, because they answer different questions. The unweighted mean (89.6%) treats a 16-cell Spinner and an 83,520-cell Button as equals; the cell-weighted figure (88.6%) is what fraction of every style cell in the corpus actually matched. Neither is quoted alone. 31/54 components are at ≥90%, 47/54 at ≥80%.
Every measured component, worst first
No component is omitted, and the worst row in the corpus is at the top. A success page that showed only its best rows would be the exact failure this project exists to catch.
| component | library | %equal | combos × states | cells |
|---|---|---|---|---|
Avatar | Polaris | 69.8 | 10 × 4 | 1,920 |
Card | Flowbite / Tailwind | 72.4 | 1 × 4 | 116 |
Checkbox | MUI | 72.5 | 6 × 4 | 1,656 |
Switch | Astryx | 77.3 | 4 × 4 | 1,536 |
Accordion | Carbon | 77.6 | 16 × 4 | 5,472 |
Switch | MUI | 77.7 | 56 × 4 | 25,088 |
Button | Carbon | 79.0 | 112 × 4 | 20,608 |
Tag | Carbon | 80.5 | 52 × 4 | 12,896 |
Checkbox | Polaris | 81.0 | 6 × 4 | 2,464 |
Tag | Polaris | 81.0 | 32 × 4 | 7,401 |
RadioButton | Polaris | 81.3 | 4 × 4 | 1,392 |
Button | Altitude | 81.5 | 5 × 4 | 1,280 |
Avatar | Altitude | 81.7 | 4 × 4 | 568 |
TextField | Polaris | 82.0 | 64 × 4 | 41,440 |
Link | Altitude | 82.4 | 4 × 4 | 432 |
Checkbox | Carbon | 84.3 | 6 × 4 | 1,776 |
Toggle | Carbon | 84.3 | 4 × 4 | 1,376 |
Alert | Flowbite / Tailwind | 84.5 | 4 × 4 | 880 |
Table | MUI | 85.2 | 2 × 4 | 6,176 |
Button | MUI | 86.4 | 126 × 4 | 27,216 |
ToggleSwitch | Flowbite / Tailwind | 88.9 | 6 × 4 | 864 |
TextInput | Carbon | 89.0 | 10 × 4 | 3,560 |
Slider | MUI | 89.4 | 12 × 4 | 7,392 |
Modal | Carbon | 90.0 | 5 × 4 | 1,305 |
Chip | MUI | 90.2 | 28 × 4 | 6,832 |
Slider | Astryx | 90.4 | 6 × 4 | 2,944 |
Badge | Flowbite / Tailwind | 90.7 | 12 × 4 | 1,968 |
Tooltip | MUI | 90.7 | 2 × 4 | 129 |
Button | Polaris | 90.8 | 240 × 4 | 83,520 |
Accordion | MUI | 91.8 | 8 × 4 | 6,400 |
ProgressBar | Polaris | 92.1 | 12 × 4 | 1,824 |
Tabs | Carbon | 93.2 | 1 × 4 | 1,240 |
Badge | Altitude | 93.8 | 10 × 4 | 1,280 |
Tabs | MUI | 93.8 | 6 × 4 | 4,320 |
Pagination | MUI | 94.0 | 1 × 4 | 796 |
Menu | MUI | 94.2 | 1 × 4 | 171 |
Chip | Altitude | 94.3 | 12 × 4 | 1,968 |
Autocomplete | MUI | 95.1 | 2 × 4 | 2,536 |
Dialog | MUI | 95.4 | 5 × 4 | 435 |
Banner | Polaris | 97.0 | 16 × 4 | 12,192 |
InlineNotification | Carbon | 97.1 | 12 × 4 | 4,944 |
Badge | Polaris | 97.5 | 60 × 4 | 23,396 |
Button | Flowbite / Tailwind | 97.6 | 50 × 4 | 6,600 |
Card | Astryx | 98.6 | 13 × 4 | 1,144 |
Button | Astryx | 99.0 | 24 × 4 | 11,520 |
Divider | Altitude | 100.0 | 2 × 4 | 144 |
Heading | Altitude | 100.0 | 14 × 4 | 392 |
IconClose | Altitude | 100.0 | 8 × 4 | 160 |
Badge | Astryx | 100.0 | 14 × 4 | 1,512 |
IconButton | Carbon | 100.0 | 40 × 4 | 1,120 |
Card | MUI | 100.0 | 4 × 4 | 704 |
Spinner | Polaris | 100.0 | 2 × 4 | 8 |
Text | Polaris | 100.0 | 379 × 4 | 24,256 |
Thumbnail | Polaris | 100.0 | 4 × 4 | 592 |
The synthetic fixture, held out of every average above
extract/computed/out/conformance/ holds 50 more scorecards at a mean of 96.1%. They are excluded from the table above entirely and must never be folded into a library mean: they are synthetic single-construct cases this repo wrote to probe one CSS or DOM feature each, not components from anyone's design system. Including them would raise the headline by averaging in a fixture we built to be measurable — the shape of every overclaim this repo has caught. The corpus is partitioned by directory before any mean is taken.
| fixture | cases | mean %equal | counted in §3? |
|---|---|---|---|
| synthetic CSS/DOM constructs | 50 | 96.1 | no |
| real third-party components | 54 | 89.6 | yes |
Fidelity — canvas → code Generated
The reverse journey, measured on a real Figma community kit. Variants are proposed from the canvas into contracts, emitted as static HTML, rendered, and scored against the exported reference image of the same variant.
| measure | value |
|---|---|
| rows in the scored table | 599 |
| statically scorable | 537 |
| mean fidelity over those | 92.70% |
| component sets | 15 |
| unscored | 62 |
The unscored rows are named rather than dropped, and they are not all one thing:
| why a row is unscored | rows |
|---|---|
| interaction-state (CSS-rendered, not statically scorable) | 58 |
| axis not carried: size=xxs | 4 |
An interaction-state rendering is produced by CSS at runtime, so a static export cannot be scored against it — that is an instrument limit. The rest is a carriage gap: an axis the pipeline did not carry. They are counted separately rather than folded into the same excuse.
| set | variants scored | mean fidelity |
|---|---|---|
toggle-base | 16 | 98.00 |
avatar-add-button | 6 | 96.69 |
avatar | 162 | 96.17 |
progress-bar | 55 | 94.02 |
button-group-base | 32 | 92.24 |
social-button | 108 | 92.06 |
button-base | 20 | 91.97 |
input-field-base | 10 | 91.63 |
avatar-label-group | 12 | 91.48 |
badge-base | 8 | 91.33 |
slider | 40 | 91.18 |
dropdown-list-item | 12 | 90.91 |
progress-circle | 16 | 85.85 |
avatar-group | 12 | 84.90 |
tooltip | 28 | 81.21 |
Denominator for that table: these are the sets in one community kit that were imported at all. The kit's un-imported sets do not appear as low scores — they do not appear.
The round trip closes — and it is not lossless Generated
Canvas → code → canvas, on the same kit. The claim is not that the round trip preserves everything. It is that it closes: it runs to completion on every component, and every fact lands in exactly one of four buckets, so a loss is a row in a table rather than an absence.
| bucket | facts | share of all facts |
|---|---|---|
| matched | 11,104 | 47.4% |
| diverged | 1,857 | 7.9% |
| loss | 4,088 | 17.5% |
| invented | 6,365 | 27.2% |
| components closed | 15 / 15 |
47.4% matched is the honest headline, and it is not high. The value of this instrument is the classification, not the ratio — and the largest single divergence class is an artifact of the comparison rather than a loss: auto-layout-inert accounts for 940 of the 960 layout.mode divergences. A frame with one child, or with children the designer positioned absolutely, has no observable auto-layout direction to read back; the engine writes a direction the original canvas did not record. It is tagged as its own class precisely so it cannot be counted as a fidelity loss. The remaining ones are real.
| bucket | tag | facts |
|---|---|---|
| diverged | auto-layout-inert | 940 |
| diverged | (untagged) | 885 |
| diverged | vector-glyph | 32 |
| loss | restructured | 2,705 |
| loss | (untagged) | 665 |
| loss | text-style-identity | 338 |
| loss | vector-glyph | 189 |
| loss | interaction-states | 137 |
| loss | url-image | 54 |
| invented | restructured | 4,336 |
| invented | dup-sibling-names | 1,710 |
| invented | cartesian-fill | 80 |
| invented | hug-vs-fixed | 72 |
| invented | zero-stroke | 60 |
| invented | zero-fill | 54 |
| invented | mixed-stroke-weight | 32 |
| invented | vector-glyph | 15 |
| invented | declared-not-drawn | 6 |
The (untagged) rows are the honest hole in this instrument: those facts are classified into a bucket but carry no reason, so nothing here can say whether they are engine defects or comparison artifacts. They are printed rather than excluded from the denominator.
The honesty instruments, counted as features Generated
These are the numbers this project is least tempted to publish and most needs to. Each one counts something the engine could not do and said so. They belong in a capability report because a conversion tool without them is not more capable — it is just quieter.
Dropped-fact receipts
When the plugin engine compiles a contract and cannot carry a fact onto the canvas, it emits a receipt naming the fact. Across the committed corpora there are 70 such receipts, and the count is pinned exactly, in both directions — fewer receipts is not automatically progress, because it is either a real fix or a refusal path that quietly stopped firing, and both require a human to look.
| corpus | dropped-fact receipts | contracts carrying one |
|---|---|---|
untitled-ui | 15 | 15 |
mui | 14 | 14 |
polaris | 12 | 12 |
carbon | 10 | 10 |
altitude | 8 | 8 |
astryx | 6 | 6 |
tailwind | 5 | 5 |
eventz-vars | 0 | 0 |
| total | 70 |
Named refusals — the construct vocabularies
Two hand-authored manifests are the independent denominators for “what can the engine be asked to do”. Both are deliberately not derived from the code that decides carriage: an instrument whose denominator comes from the same filter that decides carriage scores 100% on a channel it never opened. A construct that is neither carried nor named-refused is a hard failure of the suite — “it silently did nothing” is not an allowed outcome.
| manifest | cases | breakdown |
|---|---|---|
| canvas constructs | 91 | CARRIED 72 · LEDGERED 11 · REFUSED 8 |
| CSS / DOM frontier | 53 | CARRIED 22 · UNSUPPORTED 18 · REFUSED 11 · LOWERED 2 |
Reproducibility — the part that is not a percentage Generated
A fidelity number you cannot reproduce is an anecdote. These are the pins that make everything above re-derivable, and each is enforced by a gate rather than asserted in prose.
| pin | value | what it forbids |
|---|---|---|
| generated source, byte-identical | 265 files hashed | a contract change altering generated code without review |
| capture double-sweep identity | 104/104 runs | a capture whose second sweep disagrees with its first |
| browser captures behind the corpus | 6,317 | a floor quoted from a sample smaller than it claims |
| executable claims | 188 gates | a documented behaviour with no test |
| dropped-fact receipt count | 70 pinned exactly | honesty being switched off unnoticed — see §6 |
| doc numbers vs the repo | gated | a doc quoting a number the repo no longer produces |
| this document vs its sources | --check | this page going stale while still reading as current |
The undefined executable claims are not all “does it work”. They are classified by what they claim, and the largest classes after extraction are detection and refusal — gates that fail if the engine stops saying no. An engine that carries everything is not a better engine; it is one that has stopped telling you what it could not do.
| claim class | gates | what the class asserts |
|---|---|---|
C1-determinism | 32 | same input, same bytes out |
C2-refusal | 29 | the engine refuses BY NAME rather than guessing |
C3-detection | 46 | a defect or drift is caught, not silently absorbed |
C4-convergence | 4 | a round trip settles instead of oscillating |
C5-extraction | 58 | a fact is carried out of a real library correctly |
C6-theming | 1 | a mode/brand switch resolves to the right values |
C7-cli | 5 | the command-line surface behaves as documented |
C8-journey | 13 | an end-to-end adopter path completes |
What it costs
Not restated here, deliberately — a summary of the limitations written by the success document is a summary written by the interested party. The complete inventory is docs/23-known-limitations.md — Known Limitations, and it is the longer of the two. It answers the three questions this page cannot: how long a library takes to onboard, how much of the work is expert-configured rather than automatic, and whether any of this holds past the 6.0% in the denominator table.
This page is a rendering of docs/24-what-works.md, generated by npm run capability:report from ten committed artifacts and refused by npm run capability:fresh if the committed bytes differ from a rebuild — one of the undefined evals runs that refusal. Every table above is parsed out of that file at build time; a section or a row that stops existing fails this build by name rather than rendering a blank. The report also cross-checks its own corpus twice from independent files — 54 scorecards, 62 committed contracts, and the capture double-sweep receipts (104/104 runs) — against a document written months earlier for a different purpose, and prints any disagreement instead of resolving it toward the more flattering value.