coming soon
Review and tests tell you whether a change works. Design checks tell you whether it looks like the design — on every PR that touches UI, without a designer opening the preview.
Design tools
| Tool | |
|---|---|
| Figma | first — components, nodes and design tokens through the Figma API |
| Penpot | next — the open-source design tool, self-hostable next to Talooner |
| Sketch | planned |
| Framer | planned |
Want another one first? Tell us.
How it works
When a PR touches UI, Talooner can go past "does it work" to "does it look like the
design". The design_check executor reads the referenced Figma nodes (reference image
- design tokens), renders the changed components in the preview, and compares them — both directions:
- Drift from Figma — a component whose pixels or tokens (color, spacing, type) don't match its Figma source.
- Not in Figma — components rendered on the page that have no matching Figma component. Undocumented UI is drift too, and easy to miss in review.
It folds the result into design.* facts:
| Fact | Meaning |
|---|---|
design.figma_match | every changed component matched its Figma source |
design.pixel_drift | worst-case visual difference, 0–1 |
design.token_mismatches | design tokens that didn't match (color/primary, spacing/md, …) |
design.undocumented_count | rendered components with no Figma source |
design.undocumented_components | which ones |
design.match_confidence | calibrated confidence in the verdict |
The generated design scenario is a Jest + Playwright visual test:
// talooner-generated · design scenario: "PriceCard vs Figma node 1284:57"
import { test, expect } from '@jest/globals';
import { chromium } from 'playwright';
import { fetchFigmaNode, diffAgainstFigma, componentsOnPage } from '@talooner/figma-qa';
const DEPLOY = process.env.PREVIEW_URL;
test('PriceCard renders within 1% of its Figma component', async () => {
const ref = await fetchFigmaNode('1284:57'); // reference image + tokens
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(`${DEPLOY}/components/price-card`);
const shot = await page.locator('[data-component="price-card"]').screenshot();
const { pixelDrift, tokenMismatches } = diffAgainstFigma(shot, ref);
expect(pixelDrift).toBeLessThan(0.01);
expect(tokenMismatches).toEqual([]); // colors/spacing/type match tokens
await browser.close();
});
test('every rendered component maps to a Figma component', async () => {
const rendered = await componentsOnPage(`${DEPLOY}/pricing`); // [data-component] nodes
const documented = await fetchFigmaNode('page:pricing').then(n => n.componentNames);
const orphans = rendered.filter(c => !documented.includes(c));
expect(orphans).toEqual([]); // UI with no Figma source is a finding, not a pass
});
And the rule that catches the not-in-Figma case:
rule "Flag UI that isn't in Figma" {
for records where type == "pr"
and is "pr.touches_design"
and attr "design.undocumented_count" > 0
do require "review.design"
do comment "pr" "These rendered components have no Figma source: {attr.design.undocumented_components}. Add them to the design file, or remove them"
reason "undocumented UI"
priority MEDIUM
}
Pair it with the drift and confidence rules on the Rules page and a UI change is gated three ways: it must match Figma, contain nothing that isn't in Figma, and — when the check is unsure — go to a human.