@design-parity/action
v0.1.26
Published
Integration layer: wires resolver, adapters, candidate, checks, diff, and policy into a parity run, and surfaces the verdict (CLI now; GitHub Action surface to follow).
Readme
@design-parity/action
The integration layer — it wires every other package into one parity run and renders the verdict the bot posts on a PR.
resolver ──(Correspondence[])──┐
▼
registry[source].resolve ──(DesignReference)──┐
▼
candidate render ──(CandidateRender)──► diff + checks ──(Verdict)──┐
▼
policy direction ───────────────► ParityReport ──► markdownWhat's here (this increment)
createAdapterRegistry()— thesource → ReferenceAdaptermap (figma / stitch / claude-design). It lives here because it's the one place that imports all three drivers;coremust not depend on adapters.orchestrate()— per component: resolve the reference via its adapter, pair with the candidate render,diffthem, aggregate. Fail-soft: an adapter/diff error is captured and surfaced, never thrown, and never escalates the overall status — only real verdicts do. The parity direction decides whether a failure blocks (design-led) or is advisory (code-led).resolveRunConfig()— loads the committeddesign-map.jsonand.design-parity.json(deterministic; no model, no network).renderReport()— the single markdown comment (with a stable marker so the GitHub surface can update its own comment in place).design-parity runCLI — a local run; candidate renders come from--candidates <file>for now (reproducible offline).
GitHub Action
action.yml + dist/cli/action.js auto-select a mode from the triggering
event (mirroring the sibling compose-ai-tools apply action):
comment — a
pull_request: read the changed files, keep thedesign-map.jsoncomponents whose file changed (a PR that touches none is treated as non-UI and skipped), run the pipeline, and post/update a single verdict comment (idempotent via the report marker). Exits non-zero only when the direction blocks (design-led+ a failure). If the repo has no committeddesign-map.json(parity isn't set up), it posts a one-time notice pointing at the interactive bootstrap (design-parity-bootstrap, #11) rather than guessing the design ↔ code mapping at run time — and never blocks.baseline — a
pushto thedevelopment_branch(defaultmain): render the full mapped surface, run the pipeline, and publish the browsable artifacts — a top-levelREADME.md+index.htmllanding page (linking each component's report, with a "generated, do not edit" banner), each component's self-containedreport.htmltriptych, a machine-readableverdict.json(a versionedBaselineSummary— carriesformatVersion+$schema, validated againstschema/verdict.schema.json; seedocs/report-format.md), and — when the run exposes any design-system tokens — the aggregated table as DTCG at the stabletokens/design-system.tokens.json(linked from the index; the known location to point Claude Design's GitHub import at) — to a permanentartifact_branch(defaultdesign-parity/<dev-branch>). This gives a stable, always-current view ofmain's parity state without committing generated PNGs/HTML ontomain, and a real baseline a PR can diff its candidate against. Requirescontents: write.History accrues automatically.
publishBaselinere-parents each run's tree on the existing branch tip (a linear commit chain, fast-forward push — no force), so the artifact branch already carries one commit per run. The landing page links a per-screen History to eachreport.html's commit log. Becausereport.htmlis deterministic, a run that doesn't change a screen touches no file and adds no commit noise — the history shows exactly the runs where a screen's code or mock actually changed.skip — nothing applies (e.g. a push to a non-dev branch).
mode: baseline|comment|skip overrides the selector when needed.
# .github/workflows/design-parity.yml
on:
pull_request:
push:
branches: [main]
jobs:
parity:
runs-on: ubuntu-latest
permissions:
contents: write # baseline mode force-updates the artifact branch
pull-requests: write # comment mode posts the verdict comment
steps:
- uses: actions/checkout@v4
# ... a prior step renders candidates -> candidates.json (or bundles) ...
- uses: yschimke/design-parity/packages/action@main
with:
candidates: candidates.json # CandidateRender[] …
# candidate_bundles: out/previews # … and/or compose-preview bundles
# development_branch: main # push here → baseline mode
# artifact_branch: design-parity/mainThe selector + surface logic (selectMode, postReport,
componentsForChangedFiles, checkConclusion) and the artifact builders
(baselineSummary, renderBaselineIndex) are pure and unit-tested; the git
plumbing (publishBaseline) takes an injectable GitRunner so its
orphan/re-parent/force-push sequence is unit-tested without a real remote.
Still to come (issue #8)
- Live
compose-previewrendering in the candidate provider (today candidates come from a precomputedCandidateRender[]/ preview bundles). - Bundle + commit
dist/so the action is directly consumable (e.g. viancc) and can be pinned asyschimke/design-parity/packages/action@<tag>. - Comment mode diffing the PR candidate against the published baseline (the
verdict.jsonon the artifact branch) for regression detection.
Use
import { createAdapterRegistry, orchestrate, renderReport } from "@design-parity/action";
const report = await orchestrate({
repoRoot,
registry: createAdapterRegistry(),
correspondences, // from @design-parity/resolver
candidate: (id) => renders.get(id),
direction, // from @design-parity/policy
});
console.log(renderReport(report));