@design-parity/catalog-export

中文文档
1.18.0发布于 2026/11/07Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 9145

Turn a rendered Compose component system (compose-preview data products) into an importable design-artifact catalog: per-component sticker sheets in two variants (ideal + layout), a W3C DTCG token set, and an accessibility 'greenline' annotation layer — l

原始使用文档
文档目录6 节
  1. What it produces
  2. Use
  3. Alternate themes
  4. Layout
  5. The parity verdict ( parity/findings.json )
  6. Annotation layers, and what they promise

@design-parity/catalog-export

Turn a rendered Compose component system into an importable design-artifact catalog — the code → design-tool direction of design-parity.

Where the rest of design-parity proves a PR is at parity with a design, this package goes the other way: it takes a whole component system (Compose M3, Wear Compose M3, Glimmer, Glance, …), rendered by the upstream compose-preview CLI, and lays it out as a sticker sheet designers can import into Figma, Google Stitch, or Claude Design.

The pipeline is code-led: every value comes from the renderer's own data products, so the catalog is correct by construction. Published design kits are seed/reference only — see docs/design-artifacts/REFERENCE_KITS.md.

What it produces

For each component, in its primary modes (state × theme × size):

  • Two variants — the ideal render (the capture PNG) and the layout render (the compose/semantics-wireframe bordered view, so padding / gaps / structure are visible).
  • A greenline layer — accessibility annotations anchored to the render: issue greenlines from the renderer's a11y / contrast / i18n findings, and info spec greenlines documenting each interactive node's role and measured touch-target size.
  • A redline layer — the layout spacing spec: for each node with geometry, its box plus the content padding, inter-slot gap (Arrangement.spacedBy), and corner radius — so a component and its slots each carry an importable box + spacing, not just a picture. (Walks the semantics tree, so a slot with no semantics — a decorative icon — has no box; full slot coverage is a renderer-side follow-up.)
  • A token set — the system's resolved colors / typography / radius (shapes), exported as a W3C DTCG file plus a Figma variable-collection projection (light/dark as modes).
  • Source identity — every render retains the fully-qualified Compose previewId that produced it. This is per image, so separate light/dark or state previews remain machine-resolvable through design-map.json even when the catalog uses a friendlier component id.

The on-disk bundle is a superset of the @design-parity/adapter-bundle manifest.json, so a catalog round-trips back through the parity flow.

Use

import {
  buildCatalog,
  writeCatalog,
  type ComponentSource,
} from "@design-parity/catalog-export";

// `sources` are normalized via @design-parity/candidate's data-product mappers
// (nativeFindings, semanticsToSemanticTree, composeThemeToTokens).
const catalog = buildCatalog(
  { system: "compose-m3", title: "Compose Material 3" },
  sources satisfies ComponentSource[],
);

await writeCatalog(catalog, ".design-artifacts/compose-m3", {
  sourceRoot: "build/compose-previews",
});

Output:

catalog.json            # the index: provenance, components, variants, greenlines
tokens.dtcg.json        # the system token set (W3C DTCG)
themes/<theme>.dtcg.json # one per alternate named theme, when the system declares any
figma-variables.json    # Figma variable-collection projection
images/<component>/<variant>__<state>[__theme][__size].png

Alternate themes

tokens.dtcg.json is the system token set: the one resolved theme the stickers were rendered under. A system that declares alternate themes — a Compose @ThemeCatalog / @WearThemeCatalog provider (Brand Light, High Contrast, a watch face's night palette) — has more than one, and they differ in exactly the way a token set describes: colours, and often the typeface too, since a theme is free to swap the type scale.

Pass them as buildCatalog's fourth argument (or catalogFromCandidates' themes option) and each is written as its own DTCG file under themes/, listed in the manifest:

"tokensFile": "tokens.dtcg.json",        // still the system token set
"themes": [
  {
    "id": "com.example.BrandDarkThemeCatalog",  // the provider FQN
    "name": "Brand Dark",
    "dark": true,
    "tokensFile": "themes/com.example.branddarkthemecatalog.dtcg.json"
  }
]

They are never lifted from the renders the way themeTokens is: a component's semantics record the one theme it was rendered under, so the generator — which knows which render belongs to which declared theme — supplies them. That includes dark: the generator holds the surface the renderer composed each specimen on, so it answers once here rather than leaving every consumer to work it out from the palette and disagree. Absent means unknown, not light. A theme with a blank id or an empty token set is dropped rather than published as something a consumer can select and find nothing behind, and a repeated id keeps its first entry.

The id is the provider FQN because that is what a preview server's ?theme=theme:<providerFqn> deep link already names, so a consumer can join these tokens to the theme a page is showing without a second mapping. That is the point of publishing them at all: anything that wants to show what a theme is rather than what it is called — a picker chip painted in the theme's own colours and typeface, a per-theme Figma variable mode, a contrast audit across every theme a system ships — needs the tokens, and re-rendering to recover them is the expensive thing this export exists to avoid.

figma-variables.json stays a projection of the system token set only: fanning it per theme is a variable-modes question for the importer to answer, not one to guess at here.

Layout

ModuleRole
types.tsThe Catalog / CatalogComponent / Greenline model.
ingest.tsbuildCatalog / buildComponent from normalized inputs.
greenlines.tsFindings + semantics → the greenline annotation layer.
redlines.tsSemantics → the redline (layout) annotation layer.
annotations.tsRedlines + typography → the compose-preview-annotations/v1 manifest.
parityFindings.tsA run's verdicts → the compose-preview-parity-findings/v1 manifest, with each finding anchored to the region it is about.
manifest.tsThe pure catalog.json builder (paths only, no I/O).
figma.tsDesignTokens → a Figma variable collection.
write.tsThe one I/O step: materialize the bundle to disk.

Depends only on @design-parity/core.

The parity verdict (parity/findings.json)

The annotation layers above say what each side is. A comparison also needs what a run concluded — that a label truncates once localized, that padding is 24 where the spec asserts 16, that the two frames were never comparable — and neither an annotation nor a visual score can produce that: an annotation reports both numbers without knowing which one the spec asserts, and a percentage moves identically for a padding change and a colour change.

So parityFindings.ts projects a run's Verdicts into compose-preview-parity-findings/v1, keyed by the ids a preview server routes on and scoped by the reference each verdict compared against. writeCatalog writes it when a caller passes parityFindings — a plain sticker-sheet export has no verdicts and writes nothing, and neither does a run whose components all passed.

The interesting half is the anchors: a finding has to say where it is, in each panel's own pixel space, or the server can only print it as prose. Findings carry no geometry, so it is recovered from the trees the run already diffed — a box the check measured (detail.bounds, which the a11y checks emit) is used as it stands; a detail.label is matched against each tree by label, the same match @design-parity/report-html's overlay makes, deliberately rather than a second rule that could disagree with it; and a token finding anchors to each tree's root frame, because that is the scope of the claim. Anything else gets no anchor and reads as prose — a highlight pointing at the wrong element is worse than none, since the reader has no way to tell.

Annotation layers, and what they promise

A compare page draws two annotated columns — the render (previews) and the design reference (references) — from the same walk, so a difference between the two labels is a difference in the spec rather than in how each side was measured. Two properties keep that true, and both are visible in the output:

  • Units. A render resolves dp/sp; a design board reports its own pixels. A tree that carries a density (source px per dp) is converted into the code's units, and detail records density + sourceUnit so the original number is recoverable. A tree with no density has its own unit named (text 52.5px) rather than guessed at — quoting a 3× board's pixels as sp would invent a threefold discrepancy.
  • Provenance. Spacing a source declared is a spec. Spacing measured off child geometry — which is how a hand-placed (non-auto-layout) design frame gets a layout layer at all — is an observation, prefixed ≈ in the label and tagged detail.spacingSource = "derived".

版本与发布记录

@design-parity/catalog-export 共发布 82 个版本,首次发布于 2026/06/23;过去 12 个月发布了 82 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.18.0latest2026/11/074 天前
1.17.02026/10/2617 天前
1.16.12026/10/1824 天前
1.16.02026/10/1726 天前
1.15.02026/10/1528 天前
1.14.12026/10/1230 天前

关于 @design-parity/catalog-export 的常见问题

@design-parity/catalog-export 是什么?

Turn a rendered Compose component system (compose-preview data products) into an importable design-artifact catalog: per-component sticker sheets in two variants (ideal + layout), a W3C DTCG token set, and an accessibility 'greenline' annotation layer — l

如何安装 @design-parity/catalog-export?

在项目目录运行 npm install @design-parity/catalog-export,或使用 pnpm add / yarn add / bun add。

@design-parity/catalog-export 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@design-parity/catalog-export 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@design-parity/catalog-export 可以商用吗?

@design-parity/catalog-export 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@design-parity/catalog-export 的下载量有多少?

根据 npm 官方下载统计,@design-parity/catalog-export 在 2026/10/12 – 2026/11/10 这 30 天内被下载 9,145 次,日均约 305 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@design-parity/catalog-export 还在维护吗?

最新版本 1.18.0 发布于 2026/11/07(4 天前);至今共发布 82 个版本,其中过去 12 个月发布了 82 个;首次发布于 2026/06/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。